mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-12-25 05:08:21 -06:00
commit
155e3b1e36
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,3 +12,6 @@ Cargo.lock
|
||||||
|
|
||||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||||
*.pdb
|
*.pdb
|
||||||
|
|
||||||
|
#old source
|
||||||
|
_src
|
||||||
|
|
14
Cargo.toml
14
Cargo.toml
|
@ -3,9 +3,6 @@ name = "kubi"
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
|
|
||||||
[profile.dev.package.noise]
|
|
||||||
opt-level = 3
|
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
glium = "0.32"
|
glium = "0.32"
|
||||||
image = { version = "0.24", default_features = false, features = ["png"] }
|
image = { version = "0.24", default_features = false, features = ["png"] }
|
||||||
|
@ -14,9 +11,10 @@ env_logger = "0.10"
|
||||||
strum = { version = "0.24", features = ["derive"] }
|
strum = { version = "0.24", features = ["derive"] }
|
||||||
glam = { version = "0.22", features = ["debug-glam-assert", "mint", "fast-math"] }
|
glam = { version = "0.22", features = ["debug-glam-assert", "mint", "fast-math"] }
|
||||||
hashbrown = "0.13"
|
hashbrown = "0.13"
|
||||||
noise = "0.8"
|
|
||||||
rayon = "1.6"
|
rayon = "1.6"
|
||||||
#ordered-float = "3.4"
|
shipyard = { version = "0.6", features = ["thread_local"] }
|
||||||
|
nohash-hasher = "0.2.0"
|
||||||
[features]
|
anyhow = "1.0"
|
||||||
default = []
|
flume = "0.10"
|
||||||
|
#once_cell = "1.17"
|
||||||
|
bracket-noise = "0.8"
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
# development moved to
|
<h1 align="center">Kubi</h1>
|
||||||
# https://github.com/griffi-gh/kubi/tree/ecs-rewrite
|
work in progress
|
||||||
this branch contains the latest version before the rewrite
|
<h6 align="right"><i>~ uwu</i></h6>
|
|
@ -5,4 +5,5 @@ uniform vec4 u_color;
|
||||||
|
|
||||||
void main() {
|
void main() {
|
||||||
color = u_color;
|
color = u_color;
|
||||||
|
color -= vec4(0, 0, 0, 0.1 * sin(gl_FragCoord.x) * cos(gl_FragCoord.y));
|
||||||
}
|
}
|
10
shaders/selection_box.vert
Normal file
10
shaders/selection_box.vert
Normal file
|
@ -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.);
|
||||||
|
}
|
|
@ -1,5 +1,11 @@
|
||||||
#version 150 core
|
#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 position;
|
||||||
in vec3 normal;
|
in vec3 normal;
|
||||||
in vec2 uv;
|
in vec2 uv;
|
||||||
|
@ -7,7 +13,7 @@ in uint tex_index;
|
||||||
out vec2 v_uv;
|
out vec2 v_uv;
|
||||||
out vec3 v_normal;
|
out vec3 v_normal;
|
||||||
flat out uint v_tex_index;
|
flat out uint v_tex_index;
|
||||||
uniform vec2 position_offset;
|
uniform vec3 position_offset;
|
||||||
uniform mat4 perspective;
|
uniform mat4 perspective;
|
||||||
uniform mat4 view;
|
uniform mat4 view;
|
||||||
|
|
||||||
|
@ -15,5 +21,5 @@ void main() {
|
||||||
v_normal = normal;
|
v_normal = normal;
|
||||||
v_tex_index = tex_index;
|
v_tex_index = tex_index;
|
||||||
v_uv = uv;
|
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.);
|
||||||
}
|
}
|
37
src/block_placement.rs
Normal file
37
src/block_placement.rs
Normal file
|
@ -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<MainPlayer>,
|
||||||
|
raycast: View<LookingAtBlock>,
|
||||||
|
input: UniqueView<Inputs>,
|
||||||
|
prev_input: UniqueView<PrevInputs>,
|
||||||
|
mut world: UniqueViewMut<ChunkStorage>
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
43
src/camera.rs
Normal file
43
src/camera.rs
Normal file
|
@ -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()
|
||||||
|
}
|
130
src/camera/frustum.rs
Normal file
130
src/camera/frustum.rs
Normal file
|
@ -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<const I: usize, const J: usize>() -> usize {
|
||||||
|
I * (9 - I) / 2 + J - 1
|
||||||
|
}
|
||||||
|
fn intersection<const A: usize, const B: usize, const C: usize>(planes: &[Vec4; PLANE_COUNT], crosses: &[Vec3A; PLANE_COMBINATIONS]) -> Vec3A {
|
||||||
|
let d = Vec3A::from(planes[A]).dot(crosses[ij2k::<B, C>()]);
|
||||||
|
let res = Mat3A::from_cols(
|
||||||
|
crosses[ij2k::<B, C>()],
|
||||||
|
-crosses[ij2k::<A, C>()],
|
||||||
|
crosses[ij2k::<A, B>()],
|
||||||
|
) * vec3a(planes[A].w, planes[B].w, planes[C].w);
|
||||||
|
res * (-1. / d)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_frustum(
|
||||||
|
mut cameras: ViewMut<Camera>,
|
||||||
|
transforms: View<Transform>
|
||||||
|
) {
|
||||||
|
for (camera, _) in (&mut cameras, transforms.inserted_or_modified()).iter() {
|
||||||
|
camera.frustum = Frustum::compute(camera);
|
||||||
|
}
|
||||||
|
}
|
42
src/camera/matrices.rs
Normal file
42
src/camera/matrices.rs
Normal file
|
@ -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<Camera>,
|
||||||
|
v_transform: View<Transform>
|
||||||
|
) {
|
||||||
|
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<Camera>,
|
||||||
|
resize: View<WindowResizedEvent>,
|
||||||
|
) {
|
||||||
|
//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()
|
||||||
|
}
|
55
src/events.rs
Normal file
55
src/events.rs
Normal file
|
@ -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::<SparseSet<EventComponent>>();
|
||||||
|
}
|
52
src/fly_controller.rs
Normal file
52
src/fly_controller.rs
Normal file
|
@ -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<FlyController>,
|
||||||
|
mut transforms: ViewMut<Transform>,
|
||||||
|
inputs: UniqueView<Inputs>,
|
||||||
|
settings: UniqueView<GameSettings>,
|
||||||
|
dt: UniqueView<DeltaTime>,
|
||||||
|
) {
|
||||||
|
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<FlyController>,
|
||||||
|
mut transforms: ViewMut<Transform>,
|
||||||
|
inputs: UniqueView<Inputs>,
|
||||||
|
dt: UniqueView<DeltaTime>,
|
||||||
|
) {
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
162
src/game.rs
162
src/game.rs
|
@ -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;
|
|
||||||
});
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<RawImage2d<u8>> = 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,
|
|
||||||
}
|
|
|
@ -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<CollisionType>,
|
|
||||||
pub raycast_collision: bool,
|
|
||||||
pub render: Option<(RenderType, BlockTextures)>,
|
|
||||||
pub item: Option<Item>,
|
|
||||||
}
|
|
||||||
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<Self> {
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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()
|
|
||||||
}
|
|
|
@ -1,9 +0,0 @@
|
||||||
//TODO items
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
|
||||||
pub enum Item {
|
|
||||||
StoneBlock,
|
|
||||||
DirtBlock,
|
|
||||||
GrassBlock,
|
|
||||||
SandBlock,
|
|
||||||
}
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,7 +0,0 @@
|
||||||
use crate::game::camera::Camera;
|
|
||||||
use crate::game::physics::BasicPhysicsActor;
|
|
||||||
|
|
||||||
pub struct MainPlayer {
|
|
||||||
pub camera: Camera,
|
|
||||||
pub actor: BasicPhysicsActor,
|
|
||||||
}
|
|
|
@ -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(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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);
|
|
||||||
// }
|
|
||||||
// "#;
|
|
|
@ -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");
|
|
|
@ -1,7 +0,0 @@
|
||||||
#version 150 core
|
|
||||||
|
|
||||||
in vec2 position;
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
gl_Position = vec4(position, 0., 1.);
|
|
||||||
}
|
|
|
@ -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<IVec2, Chunk>,
|
|
||||||
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<Block> {
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<ChunkVertex>,
|
|
||||||
pub index_buffer: IndexBuffer<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Chunk {
|
|
||||||
pub position: IVec2,
|
|
||||||
pub block_data: Option<ChunkData>,
|
|
||||||
pub mesh: Option<ChunkMesh>,
|
|
||||||
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,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<IVec2, Option<JoinHandle<ChunkData>>>,
|
|
||||||
pub mesh_tasks: HashMap<IVec2, Option<JoinHandle<(Vec<ChunkVertex>, Vec<u32>)>>>,
|
|
||||||
}
|
|
||||||
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<IVec2, Chunk>, 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
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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<Vertex>,
|
|
||||||
index_buffer: Vec<u32>,
|
|
||||||
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<Vertex>, Vec<u32>) {
|
|
||||||
(self.vertex_buffer, self.index_buffer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn generate_mesh(position: IVec2, chunk_data: ChunkData, neighbors: [ChunkData; 4]) -> (Vec<Vertex>, Vec<u32>) {
|
|
||||||
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()
|
|
||||||
}
|
|
|
@ -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<Perlin> = Fbm::new(seed);
|
|
||||||
terrain_base_fbm.octaves = 6;
|
|
||||||
|
|
||||||
let mut mountainess_base_fbm: Fbm<Perlin> = 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
|
|
||||||
}
|
|
84
src/input.rs
Normal file
84
src/input.rs
Normal file
|
@ -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<VirtualKeyCode, BuildNoHashHasher<u32>>,
|
||||||
|
pub button_state: [bool; 32],
|
||||||
|
pub mouse_delta: DVec2
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn process_events(
|
||||||
|
device_events: View<InputDeviceEvent>,
|
||||||
|
mut input_state: UniqueViewMut<RawInputState>,
|
||||||
|
) {
|
||||||
|
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<RawInputState>,
|
||||||
|
mut inputs: UniqueViewMut<Inputs>,
|
||||||
|
mut prev_inputs: UniqueViewMut<PrevInputs>,
|
||||||
|
) {
|
||||||
|
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()
|
||||||
|
}
|
156
src/main.rs
156
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;
|
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() {
|
fn main() {
|
||||||
logging::init();
|
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::<UniqueViewMut<DeltaTime>>().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::<NonSendSync<UniqueView<Renderer>>>().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::<RenderTarget>().unwrap();
|
||||||
|
target.0.finish().unwrap();
|
||||||
|
|
||||||
|
//FrameEnd
|
||||||
|
world.run_workload(after_frame_end).unwrap();
|
||||||
|
},
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
27
src/player.rs
Normal file
27
src/player.rs
Normal file
|
@ -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(),
|
||||||
|
));
|
||||||
|
}
|
87
src/prefabs.rs
Normal file
87
src/prefabs.rs
Normal file
|
@ -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<UniqueView<Renderer>>
|
||||||
|
) {
|
||||||
|
storages.add_unique_non_send_sync(BlockTexturesPrefab(
|
||||||
|
load_texture2darray_prefab::<BlockTexture, _>(
|
||||||
|
"./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
|
||||||
|
)
|
||||||
|
));
|
||||||
|
}
|
31
src/prefabs/shaders.rs
Normal file
31
src/prefabs/shaders.rs
Normal file
|
@ -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;
|
44
src/prefabs/texture.rs
Normal file
44
src/prefabs/texture.rs
Normal file
|
@ -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<RawImage2d<u8>> = 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")
|
||||||
|
}
|
46
src/rendering.rs
Normal file
46
src/rendering.rs
Normal file
|
@ -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<UniqueViewMut<RenderTarget>>,
|
||||||
|
color: UniqueView<BackgroundColor>,
|
||||||
|
) {
|
||||||
|
target.0.clear_color_srgb_and_depth((color.0.x, color.0.y, color.0.z, 1.), 1.);
|
||||||
|
}
|
32
src/rendering/primitives.rs
Normal file
32
src/rendering/primitives.rs
Normal file
|
@ -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
|
||||||
|
];
|
91
src/rendering/selection_box.rs
Normal file
91
src/rendering/selection_box.rs
Normal file
|
@ -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<LookingAtBlock>,
|
||||||
|
camera: View<Camera>,
|
||||||
|
mut target: NonSendSync<UniqueViewMut<RenderTarget>>,
|
||||||
|
display: NonSendSync<UniqueView<Renderer>>,
|
||||||
|
program: NonSendSync<UniqueView<SelBoxShaderPrefab>>,
|
||||||
|
) {
|
||||||
|
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();
|
||||||
|
}
|
107
src/rendering/world.rs
Normal file
107
src/rendering/world.rs
Normal file
|
@ -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<UniqueViewMut<RenderTarget>>,
|
||||||
|
chunks: UniqueView<ChunkStorage>,
|
||||||
|
meshes: NonSendSync<UniqueView<ChunkMeshStorage>>,
|
||||||
|
program: NonSendSync<UniqueView<ChunkShaderPrefab>>,
|
||||||
|
texture: NonSendSync<UniqueView<BlockTexturesPrefab>>,
|
||||||
|
camera: View<Camera>,
|
||||||
|
) {
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
16
src/settings.rs
Normal file
16
src/settings.rs
Normal file
|
@ -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.,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
src/transform.rs
Normal file
6
src/transform.rs
Normal file
|
@ -0,0 +1,6 @@
|
||||||
|
use shipyard::Component;
|
||||||
|
use glam::Mat4;
|
||||||
|
|
||||||
|
#[derive(Component, Clone, Copy, Debug, Default)]
|
||||||
|
#[track(All)]
|
||||||
|
pub struct Transform(pub Mat4);
|
111
src/world.rs
Normal file
111
src/world.rs
Normal file
|
@ -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<IVec3, Chunk>
|
||||||
|
}
|
||||||
|
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<Block> {
|
||||||
|
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<usize, ChunkMesh, BuildNoHashHasher<usize>>,
|
||||||
|
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());
|
||||||
|
}
|
105
src/world/block.rs
Normal file
105
src/world/block.rs
Normal file
|
@ -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)
|
||||||
|
}
|
65
src/world/chunk.rs
Normal file
65
src/world/chunk.rs
Normal file
|
@ -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<ChunkVertex>,
|
||||||
|
pub index_buffer: IndexBuffer<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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<ChunkData>,
|
||||||
|
pub mesh_index: Option<usize>,
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
225
src/world/loading.rs
Normal file
225
src/world/loading.rs
Normal file
|
@ -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<GameSettings>,
|
||||||
|
v_local_player: View<MainPlayer>,
|
||||||
|
v_transform: View<Transform>,
|
||||||
|
mut vm_world: UniqueViewMut<ChunkStorage>,
|
||||||
|
) {
|
||||||
|
//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<ChunkStorage>,
|
||||||
|
mut vm_meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>
|
||||||
|
) {
|
||||||
|
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<ChunkTaskManager>,
|
||||||
|
mut world: UniqueViewMut<ChunkStorage>,
|
||||||
|
) {
|
||||||
|
if !world.is_modified() {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
//HACK: cant iterate over chunks.keys() or chunk directly!
|
||||||
|
let hashmap_keys: Vec<IVec3> = 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<ChunkTaskManager>,
|
||||||
|
mut world: UniqueViewMut<ChunkStorage>,
|
||||||
|
mut meshes: NonSendSync<UniqueViewMut<ChunkMeshStorage>>,
|
||||||
|
renderer: NonSendSync<UniqueView<Renderer>>
|
||||||
|
) {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
138
src/world/mesh.rs
Normal file
138
src/world/mesh.rs
Normal file
|
@ -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<ChunkVertex>,
|
||||||
|
index_buffer: Vec<u32>,
|
||||||
|
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<ChunkVertex>, Vec<u32>) {
|
||||||
|
(self.vertex_buffer, self.index_buffer)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn generate_mesh(data: MeshGenData) -> (Vec<ChunkVertex>, Vec<u32>) {
|
||||||
|
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()
|
||||||
|
}
|
34
src/world/mesh/data.rs
Normal file
34
src/world/mesh/data.rs
Normal file
|
@ -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<MeshGenData> {
|
||||||
|
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(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
122
src/world/neighbors.rs
Normal file
122
src/world/neighbors.rs
Normal file
|
@ -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<AllChunkNeighbors<'a>> {
|
||||||
|
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<AllChunkNeighborsMut<'a>> 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<AllChunkNeighbors<'a>> 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<AllChunkNeighborsMut<'a>> 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<AllChunkNeighbors> {
|
||||||
|
self.neighbors(coords).all()
|
||||||
|
}
|
||||||
|
pub fn neighbors_all_mut(&mut self, coords: IVec3) -> Option<AllChunkNeighborsMut> {
|
||||||
|
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 })
|
||||||
|
}
|
||||||
|
}
|
65
src/world/raycast.rs
Normal file
65
src/world/raycast.rs
Normal file
|
@ -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<f32>) -> Option<RaycastReport> {
|
||||||
|
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<RaycastReport>);
|
||||||
|
|
||||||
|
pub fn update_raycasts(
|
||||||
|
transform: View<Transform>,
|
||||||
|
mut raycast: ViewMut<LookingAtBlock>,
|
||||||
|
world: UniqueView<ChunkStorage>,
|
||||||
|
) {
|
||||||
|
//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.)));
|
||||||
|
}
|
||||||
|
}
|
64
src/world/tasks.rs
Normal file
64
src/world/tasks.rs
Normal file
|
@ -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<ChunkVertex>,
|
||||||
|
indexes: Vec<u32>
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Unique)]
|
||||||
|
pub struct ChunkTaskManager {
|
||||||
|
channel: (Sender<ChunkTaskResponse>, Receiver<ChunkTaskResponse>),
|
||||||
|
pool: ThreadPool,
|
||||||
|
}
|
||||||
|
impl ChunkTaskManager {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
channel: flume::unbounded::<ChunkTaskResponse>(), //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<ChunkTaskResponse> {
|
||||||
|
self.channel.1.try_recv().ok()
|
||||||
|
}
|
||||||
|
}
|
49
src/world/worldgen.rs
Normal file
49
src/world/worldgen.rs
Normal file
|
@ -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
|
||||||
|
}
|
Loading…
Reference in a new issue