mirror of
https://github.com/griffi-gh/kubi.git
synced 2024-12-25 13:18:21 -06:00
start rewrite
This commit is contained in:
parent
3a446b675b
commit
097bd50e4b
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -12,3 +12,6 @@ Cargo.lock
|
|||
|
||||
# MSVC Windows builds of rustc generate these, which store debugging information
|
||||
*.pdb
|
||||
|
||||
#old source
|
||||
_src
|
||||
|
|
|
@ -16,7 +16,7 @@ glam = { version = "0.22", features = ["debug-glam-assert", "mint", "fast-math"]
|
|||
hashbrown = "0.13"
|
||||
noise = "0.8"
|
||||
rayon = "1.6"
|
||||
#ordered-float = "3.4"
|
||||
specs = { version = "0.18", features = ["specs-derive"] }
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
|
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,15 +0,0 @@
|
|||
#version 150 core
|
||||
|
||||
in vec3 v_normal;
|
||||
in vec2 v_uv;
|
||||
flat in uint v_tex_index;
|
||||
out vec4 color;
|
||||
uniform sampler2DArray tex;
|
||||
|
||||
void main() {
|
||||
// base color from texture
|
||||
color = texture(tex, vec3(v_uv, v_tex_index));
|
||||
//basic "lighting"
|
||||
float light = abs(v_normal.x) + .8 * abs(v_normal.y) + .6 * abs(v_normal.z);
|
||||
color *= vec4(vec3(light), 1.);
|
||||
}
|
|
@ -1,19 +0,0 @@
|
|||
#version 150 core
|
||||
|
||||
in vec3 position;
|
||||
in vec3 normal;
|
||||
in vec2 uv;
|
||||
in uint tex_index;
|
||||
out vec2 v_uv;
|
||||
out vec3 v_normal;
|
||||
flat out uint v_tex_index;
|
||||
uniform vec2 position_offset;
|
||||
uniform mat4 perspective;
|
||||
uniform mat4 view;
|
||||
|
||||
void main() {
|
||||
v_normal = normal;
|
||||
v_tex_index = tex_index;
|
||||
v_uv = uv;
|
||||
gl_Position = perspective * view * (vec4(position, 1.0) + vec4(position_offset.x, 0., position_offset.y, 0.));
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
#version 150 core
|
||||
|
||||
out vec4 color;
|
||||
uniform vec4 u_color;
|
||||
|
||||
void main() {
|
||||
color = u_color;
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -1,45 +0,0 @@
|
|||
//! Custom env_logger options and styling
|
||||
|
||||
use env_logger::{fmt::Color, Builder, Env};
|
||||
use log::Level;
|
||||
use std::io::Write;
|
||||
|
||||
pub fn init() {
|
||||
let mut env = Env::default();
|
||||
if cfg!(debug_assertions) {
|
||||
env = env.filter_or("RUST_LOG", "trace");
|
||||
}
|
||||
Builder::from_env(env)
|
||||
.format(|buf, record| {
|
||||
let mut level_style = buf.style();
|
||||
level_style.set_color(match record.level() {
|
||||
Level::Error => Color::Red,
|
||||
Level::Warn => Color::Yellow,
|
||||
_ => Color::Blue
|
||||
}).set_bold(true);
|
||||
|
||||
let mut location_style = buf.style();
|
||||
location_style.set_bold(true);
|
||||
location_style.set_dimmed(true);
|
||||
|
||||
let mut location_line_style = buf.style();
|
||||
location_line_style.set_dimmed(true);
|
||||
|
||||
writeln!(
|
||||
buf,
|
||||
"{} {:<50}\t{}{}{}",
|
||||
level_style.value(match record.level() {
|
||||
Level::Error => "[e]",
|
||||
Level::Warn => "[w]",
|
||||
Level::Info => "[i]",
|
||||
Level::Debug => "[d]",
|
||||
Level::Trace => "[t]",
|
||||
}),
|
||||
format!("{}", record.args()),
|
||||
location_style.value(record.target()),
|
||||
location_line_style.value(" :"),
|
||||
location_line_style.value(record.line().unwrap_or(0))
|
||||
)
|
||||
})
|
||||
.init();
|
||||
}
|
|
@ -1,7 +1,3 @@
|
|||
mod game;
|
||||
mod logging;
|
||||
|
||||
fn main() {
|
||||
logging::init();
|
||||
game::run();
|
||||
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue