├── .gitignore ├── src ├── traits │ ├── mod.rs │ ├── game_chunk.rs │ └── game_world.rs ├── utils │ ├── num_utils.rs │ ├── mod.rs │ ├── simplex_utils.rs │ ├── name_utils.rs │ ├── world_utils.rs │ ├── vector_utils.rs │ └── chunk_utils.rs ├── core │ ├── mod.rs │ ├── face.rs │ ├── window_mode.rs │ ├── coord_map.rs │ ├── block_map.rs │ ├── block_type.rs │ ├── player.rs │ ├── world.rs │ └── chunk.rs ├── multiplayer │ ├── direction.rs │ ├── mod.rs │ ├── event.rs │ ├── server_player.rs │ ├── server_state.rs │ ├── rc_message.rs │ ├── server_chunk.rs │ ├── server_connection.rs │ └── server_world.rs ├── opengl │ ├── mod.rs │ ├── face_uvs.rs │ ├── vertex_array.rs │ ├── vertex_buffer.rs │ ├── element_buffer.rs │ ├── texture.rs │ ├── player_model.rs │ ├── tex_quad.rs │ ├── depth_framebuffer.rs │ ├── skybox.rs │ ├── button.rs │ ├── cloud.rs │ ├── framebuffer.rs │ ├── camera.rs │ ├── cube.rs │ ├── input.rs │ ├── shader.rs │ └── text_renderer.rs └── main.rs ├── screenshot.png ├── assets ├── textures │ ├── cloud.png │ ├── icon.png │ ├── input.png │ ├── water.png │ ├── button.png │ ├── textures.png │ ├── button_wide.png │ └── player_skin.png ├── font │ └── OldSchoolAdventures.ttf └── shaders │ ├── cloud │ ├── cloud_fragment.frag │ └── cloud_vertex.vert │ ├── skybox │ ├── skybox_fragment.frag │ └── skybox_vertex.vert │ ├── depth_map │ ├── depth_fragment.frag │ └── depth_vertex.vert │ ├── texquad │ ├── texquad_fragment.frag │ └── texquad_vertex.vert │ ├── player │ ├── player_fragment.frag │ └── player_vertex.vert │ ├── text │ ├── text_fragment.frag │ ├── text_fragment3d.frag │ ├── text_vertex.vert │ └── text_vertex3d.vert │ ├── framebuffer │ ├── fb_fragment.frag │ └── fb_vertex.vert │ └── voxal │ ├── vertex.vert │ ├── fragment.frag │ └── geometry.geom ├── Cargo.toml └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /target 2 | game_data 3 | .DS_Store -------------------------------------------------------------------------------- /src/traits/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod game_world; 2 | pub mod game_chunk; -------------------------------------------------------------------------------- /screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/screenshot.png -------------------------------------------------------------------------------- /assets/textures/cloud.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/cloud.png -------------------------------------------------------------------------------- /assets/textures/icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/icon.png -------------------------------------------------------------------------------- /assets/textures/input.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/input.png -------------------------------------------------------------------------------- /assets/textures/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/water.png -------------------------------------------------------------------------------- /assets/textures/button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/button.png -------------------------------------------------------------------------------- /assets/textures/textures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/textures.png -------------------------------------------------------------------------------- /assets/textures/button_wide.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/button_wide.png -------------------------------------------------------------------------------- /assets/textures/player_skin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/textures/player_skin.png -------------------------------------------------------------------------------- /assets/font/OldSchoolAdventures.ttf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/profsucrose/rustycraft/HEAD/assets/font/OldSchoolAdventures.ttf -------------------------------------------------------------------------------- /assets/shaders/cloud/cloud_fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 color; 3 | 4 | void main() { 5 | color = vec4(1.0); 6 | } -------------------------------------------------------------------------------- /src/utils/num_utils.rs: -------------------------------------------------------------------------------- 1 | pub fn distance(x1: i32, z1: i32, x2: i32, z2: i32) -> f32 { 2 | (((x1 - x2).pow(2) + (z1 - z2).pow(2)) as f32).sqrt() 3 | } -------------------------------------------------------------------------------- /assets/shaders/skybox/skybox_fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 color; 3 | 4 | in vec3 pos; 5 | 6 | void main() { 7 | color = vec4(pos, 1.0); 8 | } -------------------------------------------------------------------------------- /src/utils/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod world_utils; 2 | pub mod chunk_utils; 3 | pub mod name_utils; 4 | pub mod vector_utils; 5 | pub mod simplex_utils; 6 | pub mod num_utils; -------------------------------------------------------------------------------- /assets/shaders/depth_map/depth_fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // fragment depth is already automatically determined, 4 | // so use empty fragment shader 5 | void main() {} -------------------------------------------------------------------------------- /src/core/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod block_map; 2 | pub mod block_type; 3 | pub mod chunk; 4 | pub mod coord_map; 5 | pub mod face; 6 | pub mod world; 7 | pub mod player; 8 | pub mod window_mode; -------------------------------------------------------------------------------- /src/core/face.rs: -------------------------------------------------------------------------------- 1 | // each possible cube face direction 2 | #[derive(Debug, Clone, Copy)] 3 | pub enum Face { 4 | Top, 5 | Bottom, 6 | Left, 7 | Right, 8 | Front, 9 | Back 10 | } -------------------------------------------------------------------------------- /src/multiplayer/direction.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | #[derive(Debug, Serialize, Deserialize)] 4 | pub enum Direction { 5 | Right, 6 | Left, 7 | Forward, 8 | Backward 9 | } -------------------------------------------------------------------------------- /src/core/window_mode.rs: -------------------------------------------------------------------------------- 1 | // enum representing window state 2 | #[derive(Debug, PartialEq)] 3 | pub enum WindowMode { 4 | Title, 5 | OpenWorld, 6 | ConnectToServer, 7 | InWorld, 8 | InServer 9 | } -------------------------------------------------------------------------------- /src/multiplayer/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod rc_message; 2 | pub mod direction; 3 | pub mod event; 4 | pub mod server_connection; 5 | pub mod server_world; 6 | pub mod server_chunk; 7 | pub mod server_state; 8 | pub mod server_player; -------------------------------------------------------------------------------- /assets/shaders/depth_map/depth_vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 a_pos; 3 | 4 | uniform mat4 light_space_matrix; 5 | 6 | void main() { 7 | gl_Position = light_space_matrix * vec4(a_pos, 1.0); 8 | } -------------------------------------------------------------------------------- /src/multiplayer/event.rs: -------------------------------------------------------------------------------- 1 | use super::rc_message::RustyCraftMessage; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Debug, Serialize, Deserialize)] 5 | pub struct RustyCraftEvent { 6 | pub sender: String, 7 | pub message: RustyCraftMessage 8 | } -------------------------------------------------------------------------------- /assets/shaders/texquad/texquad_fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TexCoords; 5 | 6 | uniform sampler2D tex; 7 | uniform float alpha; 8 | 9 | void main() { 10 | FragColor = vec4(texture(tex, TexCoords).rgb, alpha); 11 | } -------------------------------------------------------------------------------- /assets/shaders/player/player_fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 color; 3 | 4 | in vec2 tex_coords; 5 | 6 | uniform sampler2D player_texture; 7 | 8 | void main() { 9 | color = vec4(texture(player_texture, tex_coords).rgb, 1.0); 10 | // color = vec4(1.0, 0.0, 0.0, 1.0); 11 | } -------------------------------------------------------------------------------- /src/utils/simplex_utils.rs: -------------------------------------------------------------------------------- 1 | use noise::{NoiseFn, OpenSimplex}; 2 | 3 | pub fn sample(x: f32, z: f32, simplex: OpenSimplex) -> f32 { 4 | // noise library returns noise value in range -1.0 to 1.0, 5 | // so shift over to 0.0 to 1.0 range 6 | ((simplex.get([x as f64, z as f64]) + 1.0) / 2.0) as f32 7 | } -------------------------------------------------------------------------------- /assets/shaders/text/text_fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | in vec2 TexCoord; 3 | out vec4 color; 4 | 5 | uniform sampler2D text; 6 | uniform vec3 textColor; 7 | 8 | void main() { 9 | if (texture(text, TexCoord).r < 0.1) { 10 | discard; 11 | } 12 | color = vec4(textColor, 1.0); 13 | } -------------------------------------------------------------------------------- /assets/shaders/text/text_fragment3d.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | in vec2 TexCoord; 3 | out vec4 color; 4 | 5 | uniform sampler2D text; 6 | uniform vec3 textColor; 7 | 8 | void main() { 9 | if (texture(text, TexCoord).r < 0.1) { 10 | discard; 11 | } 12 | color = vec4(textColor, 1.0); 13 | } -------------------------------------------------------------------------------- /assets/shaders/cloud/cloud_vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 a_pos; 3 | 4 | out vec3 pos; 5 | 6 | uniform mat4 projection; 7 | uniform mat4 view; 8 | uniform mat4 model; 9 | 10 | void main() { 11 | gl_Position = projection * view * model * vec4(a_pos, 1.0); 12 | pos = a_pos; 13 | } -------------------------------------------------------------------------------- /assets/shaders/texquad/texquad_vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec2 aPos; 3 | layout (location = 1) in vec2 aTexCoords; 4 | 5 | out vec2 TexCoords; 6 | uniform mat4 projection; 7 | 8 | void main() { 9 | gl_Position = projection * vec4(aPos.xy, 0.0, 1.0); 10 | TexCoords = aTexCoords; 11 | } 12 | -------------------------------------------------------------------------------- /assets/shaders/text/text_vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec4 vertex; // 3 | out vec2 TexCoord; 4 | 5 | uniform mat4 projection; 6 | uniform mat4 model; 7 | 8 | void main() { 9 | gl_Position = projection * model * vec4(vertex.xy, 0.0, 1.0); 10 | TexCoord = vertex.zw; 11 | } -------------------------------------------------------------------------------- /assets/shaders/framebuffer/fb_fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TexCoords; 5 | 6 | uniform sampler2D screenTexture; 7 | 8 | void main() { 9 | //float contrast = 1.0; 10 | //vec4 result = texture(screenTexture, TexCoords) * (1.0 + contrast) / 1.0; 11 | FragColor = texture(screenTexture, TexCoords); 12 | } -------------------------------------------------------------------------------- /assets/shaders/text/text_vertex3d.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 position; 3 | layout (location = 1) in vec2 aTexCoord; 4 | out vec2 TexCoord; 5 | 6 | uniform mat4 model; 7 | uniform mat4 view; 8 | uniform mat4 projection; 9 | 10 | void main() { 11 | gl_Position = projection * view * model * vec4(position, 1.0); 12 | TexCoord = aTexCoord; 13 | } -------------------------------------------------------------------------------- /assets/shaders/player/player_vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 position; 3 | layout (location = 1) in vec2 a_texcoords; 4 | 5 | uniform mat4 model; 6 | uniform mat4 view; 7 | uniform mat4 projection; 8 | 9 | out vec2 tex_coords; 10 | 11 | void main() { 12 | tex_coords = a_texcoords; 13 | gl_Position = projection * view * model * vec4(position, 1.0); 14 | } -------------------------------------------------------------------------------- /assets/shaders/framebuffer/fb_vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec2 aPos; 3 | layout (location = 1) in vec2 aTexCoords; 4 | 5 | out vec2 TexCoords; 6 | 7 | void main() { 8 | // simple passthrough vertex shader for rendering 9 | // a textured quad with normal NDC for framebuffer 10 | gl_Position = vec4(aPos.x, aPos.y, 0.0, 1.0); 11 | TexCoords = aTexCoords; 12 | } 13 | -------------------------------------------------------------------------------- /src/opengl/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod camera; 2 | pub mod shader; 3 | pub mod text_renderer; 4 | pub mod texture; 5 | pub mod vertex_array; 6 | pub mod vertex_buffer; 7 | pub mod framebuffer; 8 | pub mod tex_quad; 9 | pub mod button; 10 | pub mod input; 11 | pub mod cube; 12 | pub mod element_buffer; 13 | pub mod depth_framebuffer; 14 | pub mod face_uvs; 15 | pub mod player_model; 16 | pub mod skybox; 17 | pub mod cloud; -------------------------------------------------------------------------------- /src/traits/game_chunk.rs: -------------------------------------------------------------------------------- 1 | use crate::core::{block_map::BlockMap, chunk::CHUNK_HEIGHT}; 2 | 3 | pub trait GameChunk { 4 | fn get_blocks(&self) -> &BlockMap; 5 | fn highest_in_column(&self, x: usize, z: usize) -> usize { 6 | self.highest_in_column_from_y(x, CHUNK_HEIGHT - 1, z) 7 | } 8 | fn highest_in_column_from_y(&self, x: usize, y: usize, z: usize) -> usize { 9 | self.get_blocks().highest_in_column_from_y(x, y, z) 10 | } 11 | } -------------------------------------------------------------------------------- /src/opengl/face_uvs.rs: -------------------------------------------------------------------------------- 1 | pub struct FaceUVs { 2 | pub left: f32, 3 | pub bottom: f32, 4 | pub right: f32, 5 | pub top: f32 6 | } 7 | 8 | impl FaceUVs { 9 | pub fn new(left: f32, bottom: f32, right: f32, top: f32, width: f32, height: f32) -> FaceUVs { 10 | FaceUVs { 11 | left: left / width, 12 | bottom: 1.0 - bottom / height, 13 | right: right / width, 14 | top: 1.0 - top / height 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /src/opengl/vertex_array.rs: -------------------------------------------------------------------------------- 1 | use gl::types::*; 2 | pub struct VertexArray { 3 | vao: GLuint 4 | } 5 | 6 | impl VertexArray { 7 | pub unsafe fn new() -> VertexArray { 8 | let mut vao = 0; 9 | gl::GenVertexArrays(1, &mut vao); 10 | VertexArray { vao } 11 | } 12 | 13 | pub unsafe fn bind(&self) { 14 | gl::BindVertexArray(self.vao); 15 | } 16 | 17 | pub unsafe fn unbind() { 18 | gl::BindVertexArray(0); 19 | } 20 | } -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | #[allow(dead_code)] 2 | [package] 3 | name = "rustycraft" 4 | version = "0.1.0" 5 | authors = ["ProfSucrose "] 6 | edition = "2018" 7 | 8 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 9 | 10 | [dependencies] 11 | cgmath = "0.16.1" 12 | gl = "0.10.0" 13 | glutin = "0.27.0" 14 | image = "0.19.0" 15 | freetype-rs = "0.26.0" 16 | noise = "0.7.0" 17 | rand = "0.8.0" 18 | serde_json = "1.0" 19 | serde = { version = "1.0", features = ["derive"] } 20 | adjective_adjective_animal = "0.1.0" 21 | -------------------------------------------------------------------------------- /src/utils/name_utils.rs: -------------------------------------------------------------------------------- 1 | use adjective_adjective_animal::Generator; 2 | 3 | pub fn gen_name() -> String { 4 | // get adjective-animal, omit first adjective 5 | // for length constraints 6 | let name = Generator::default().next().unwrap(); 7 | let mut result = String::new(); 8 | let mut adding_chars = false; 9 | for ch in name.chars().skip(1) { 10 | if !adding_chars && ch.is_uppercase() { 11 | adding_chars = true; 12 | } 13 | 14 | if adding_chars { 15 | result.push(ch); 16 | } 17 | } 18 | result 19 | } -------------------------------------------------------------------------------- /src/utils/world_utils.rs: -------------------------------------------------------------------------------- 1 | pub fn localize_coords_to_chunk(world_x: i32, world_z: i32) -> (i32, i32, usize, usize) { 2 | let mut chunk_x = (world_x + if world_x < 0 { 1 } else { 0 }) / 16; 3 | if world_x < 0 { 4 | chunk_x -= 1; 5 | } 6 | 7 | let mut chunk_z = (world_z + if world_z < 0 { 1 } else { 0 }) / 16; 8 | if world_z < 0 { 9 | chunk_z -= 1; 10 | } 11 | 12 | let local_x = ((chunk_x.abs() * 16 + world_x) % 16).abs() as usize; 13 | let local_z = ((chunk_z.abs() * 16 + world_z) % 16).abs() as usize; 14 | (chunk_x, chunk_z, local_x, local_z) 15 | } -------------------------------------------------------------------------------- /src/multiplayer/server_player.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Vector3; 2 | 3 | #[derive(Debug)] 4 | pub struct ServerPlayer { 5 | pub id: String, 6 | pub name: String, 7 | pub position: Vector3, 8 | pub rotation: Vector3, 9 | pub yaw: f32, 10 | pub pitch: f32 11 | //pub skin: Texture 12 | } 13 | 14 | impl ServerPlayer { 15 | pub fn new(id: String, name: String, x: f32, y: f32, z: f32, pitch: f32, yaw: f32) -> ServerPlayer { 16 | ServerPlayer { 17 | id, 18 | name, 19 | position: Vector3::new(x, y, z), 20 | rotation: Vector3::new(0.0, 0.0, -1.0), 21 | pitch, 22 | yaw 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /src/utils/vector_utils.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{Angle, Deg, Vector3, InnerSpace}; 2 | 3 | pub fn get_direction_from_mouse_move(sensitivity: f32, yaw: f32, pitch: f32, x_offset: f32, y_offset: f32) -> (f32, f32, Vector3) { 4 | let x_offset = x_offset * sensitivity; 5 | let y_offset = y_offset * sensitivity; 6 | 7 | let yaw = yaw + x_offset; 8 | let mut pitch = pitch + y_offset; 9 | 10 | // clamp pitch 11 | if pitch > 89.0 { 12 | pitch = 89.0; 13 | } else if pitch < -89.0 { 14 | pitch = -89.0; 15 | } 16 | 17 | let direction = Vector3::new( 18 | Deg(yaw).cos() * Deg(pitch).cos(), 19 | Deg(pitch).sin(), 20 | Deg(yaw).sin() * Deg(pitch).cos() 21 | ); 22 | (yaw, pitch, direction.normalize()) 23 | } -------------------------------------------------------------------------------- /assets/shaders/voxal/vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 aPos; 3 | layout (location = 1) in float aFrontUV; 4 | layout (location = 2) in float aRightUV; 5 | layout (location = 3) in float aBackUV; 6 | layout (location = 4) in float aBottomUV; 7 | layout (location = 5) in float aLeftUV; 8 | layout (location = 6) in float aTopUV; 9 | layout (location = 7) in float aFacesToDraw; 10 | 11 | out VS_OUT { 12 | float[6] blockUVIndices; 13 | int facesToDraw; 14 | } vs_out; 15 | 16 | void main() { 17 | gl_Position = vec4(aPos, 1.0); 18 | // TexCoord = aTexCoord; 19 | vs_out.blockUVIndices = float[6](aFrontUV, aRightUV, aBackUV, aBottomUV, aLeftUV, aTopUV); 20 | vs_out.facesToDraw = int(aFacesToDraw); 21 | // position = aPos; 22 | } -------------------------------------------------------------------------------- /src/multiplayer/server_state.rs: -------------------------------------------------------------------------------- 1 | use std::{collections::HashMap, sync::{Arc, Mutex}}; 2 | 3 | use crate::multiplayer::server_world::ServerWorld; 4 | 5 | use super::server_player::ServerPlayer; 6 | 7 | #[derive(Clone)] 8 | pub struct ServerState { 9 | pub client_id: Arc>, 10 | pub world: Arc>, 11 | pub players: Arc>>, 12 | pub chat_stack: Arc>>, 13 | } 14 | 15 | impl ServerState { 16 | pub fn new(world: Arc>) -> ServerState { 17 | ServerState { 18 | client_id: Arc::new(Mutex::new(String::new())), 19 | world, 20 | players: Arc::new(Mutex::new(HashMap::new())), 21 | chat_stack: Arc::new(Mutex::new(Vec::new())) 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /assets/shaders/skybox/skybox_vertex.vert: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (location = 0) in vec3 a_pos; 3 | 4 | out vec3 pos; 5 | 6 | uniform mat4 projection; 7 | uniform mat4 view; 8 | 9 | void main() { 10 | // remove translation by converting to mat3 11 | // and then back to mat4 12 | mat4 v = mat4(mat3(view)); 13 | 14 | // map point on unit cube to unit sphere for continuous skybox 15 | // (formula credit to https://mathproofs.blogspot.com/2005/07/mapping-cube-to-sphere.html) 16 | // vec3 p = vec3(a_pos.x * 2.0, a_pos.y * 2.0, a_pos.z * 2.0); 17 | // vec3 local_position = vec3( 18 | // p.x * sqrt(1.0 - (pow(p.z, 2.0) / 1000.0) + (pow(p.y, 2.0) * pow(p.z, 2.0) / 3.0)), 19 | // p.y * sqrt(1.0 - (pow(p.x, 2.0) / 1000.0) + (pow(p.z, 2.0) * pow(p.x, 2.0) / 3.0)), 20 | // p.z * sqrt(1.0 - (pow(p.y, 2.0) / 1000.0) + (pow(p.x, 2.0) * pow(p.y, 2.0) / 3.0)) 21 | // ); 22 | 23 | gl_Position = projection * v * vec4(a_pos, 1.0); 24 | pos = a_pos; 25 | } -------------------------------------------------------------------------------- /src/multiplayer/rc_message.rs: -------------------------------------------------------------------------------- 1 | use serde::{Deserialize, Serialize}; 2 | 3 | use crate::{core::block_type::BlockType, multiplayer::direction::Direction}; 4 | 5 | #[derive(Debug, Serialize, Deserialize)] 6 | pub enum RustyCraftMessage { 7 | Movement { direction: Direction }, 8 | PlayerJoin { name: String }, 9 | PlayerInit { name: String, x: f32, y: f32, z: f32 }, 10 | PlayerDirection { yaw: f32, pitch: f32 }, 11 | PlayerPosition { x: f32, y: f32, z: f32 }, 12 | SetBlock { block: BlockType, world_x: i32, world_y: i32, world_z: i32 }, 13 | GetChunks { coords: Vec<(i32, i32)> }, 14 | ChatMessage { content: String }, 15 | 16 | // echo connection and players id to client 17 | // to avoid rendering own model and get data 18 | // for all players currently on the server 19 | ConnectionData { id: String, players: Vec<(String, String, f32, f32, f32, f32, f32)> /* (x, y, z, yaw, pitch) */ }, 20 | 21 | // serialized chunk_blocks in the form of Vec<(usize, usize, usize, usize)> 22 | // stored as string so serialized chunk blocks can be memoized 23 | ChunkData { chunks: Vec<(i32, i32, String)> }, 24 | Disconnect 25 | } -------------------------------------------------------------------------------- /src/core/coord_map.rs: -------------------------------------------------------------------------------- 1 | use std::collections::HashMap; 2 | 3 | // maps two integers to a generic 4 | #[derive(Clone)] 5 | pub struct CoordMap { 6 | map: HashMap> // x by z 7 | } 8 | 9 | impl CoordMap { 10 | pub fn new() -> CoordMap { 11 | CoordMap { map: HashMap::new() } 12 | } 13 | 14 | pub fn get(&self, x: i32, z: i32) -> Option<&T> { 15 | let item = self.map.get(&x); 16 | if item.is_none() { 17 | return None 18 | } 19 | 20 | item.unwrap().get(&z) 21 | } 22 | 23 | pub fn get_mut(&mut self, x: i32, z: i32) -> Option<&mut T> { 24 | let item = self.map.get_mut(&x); 25 | if item.is_none() { 26 | return None 27 | } 28 | 29 | item.unwrap().get_mut(&z) 30 | } 31 | 32 | pub fn contains(&self, x: i32, z: i32) -> bool { 33 | let item = self.map.get(&x); 34 | if item.is_none() { 35 | return false 36 | } 37 | 38 | item.unwrap().contains_key(&z) 39 | } 40 | 41 | pub fn insert(&mut self, x: i32, z: i32, value: T) { 42 | if !self.map.contains_key(&x) { 43 | self.map.insert(x, HashMap::new()); 44 | } 45 | self.map.get_mut(&x).unwrap().insert(z, value); 46 | } 47 | } -------------------------------------------------------------------------------- /src/traits/game_world.rs: -------------------------------------------------------------------------------- 1 | use crate::{core::block_type::BlockType, traits::game_chunk::GameChunk}; 2 | use crate::utils::world_utils::localize_coords_to_chunk; 3 | 4 | pub trait GameWorld { 5 | fn get_block(&self, x: i32, y: i32, z: i32) -> Option; 6 | fn get_game_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&dyn GameChunk>; 7 | fn moveable(&self, world_x: i32, world_y: i32, world_z: i32) -> bool { 8 | let block = self.get_block(world_x, world_y, world_z); 9 | match block { 10 | Some(block) => block == BlockType::Air || block == BlockType::Water, 11 | None => false 12 | } 13 | } 14 | fn highest_in_column(&self, world_x: i32, world_z: i32) -> Option { 15 | let (chunk_x, chunk_z, local_x, local_z) = localize_coords_to_chunk(world_x, world_z); 16 | let chunk = self.get_game_chunk(chunk_x, chunk_z); 17 | if chunk.is_none() { 18 | return None 19 | } 20 | 21 | Some(chunk.unwrap().highest_in_column(local_x, local_z)) 22 | } 23 | fn highest_in_column_from_y(&self, world_x: i32, world_y: i32, world_z: i32) -> Option { 24 | let (chunk_x, chunk_z, local_x, local_z) = localize_coords_to_chunk(world_x, world_z); 25 | let chunk = self.get_game_chunk(chunk_x, chunk_z); 26 | if chunk.is_none() { 27 | return None 28 | } 29 | 30 | Some(chunk.unwrap().highest_in_column_from_y(local_x, world_y as usize, local_z)) 31 | } 32 | } -------------------------------------------------------------------------------- /src/opengl/vertex_buffer.rs: -------------------------------------------------------------------------------- 1 | use gl::types::*; 2 | use std::ffi::c_void; 3 | 4 | pub struct VertexBuffer { 5 | vbo: GLuint, 6 | attribute_index: u32, 7 | attribute_offset: usize 8 | } 9 | 10 | impl VertexBuffer { 11 | pub unsafe fn new() -> VertexBuffer { 12 | let mut vbo = 0; 13 | gl::GenBuffers(1, &mut vbo); 14 | VertexBuffer { vbo, attribute_index: 0, attribute_offset: 0 } 15 | } 16 | 17 | pub unsafe fn unbind() { 18 | gl::BindBuffer(gl::ARRAY_BUFFER, 0); 19 | } 20 | 21 | pub unsafe fn bind(&self) { 22 | gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); 23 | } 24 | 25 | pub unsafe fn add_float_attribute(&mut self, length: usize, stride: usize) { 26 | gl::VertexAttribPointer( 27 | self.attribute_index, 28 | length as GLint, 29 | gl::FLOAT, 30 | gl::FALSE, 31 | (stride * std::mem::size_of::()) as GLsizei, 32 | if self.attribute_offset == 0 { 33 | std::ptr::null() 34 | } else { 35 | (self.attribute_offset * std::mem::size_of::()) as *const c_void 36 | } 37 | ); 38 | gl::EnableVertexAttribArray(self.attribute_index); 39 | self.attribute_index += 1; 40 | self.attribute_offset += length; 41 | } 42 | 43 | pub unsafe fn set_data(&self, vertices: &Vec, flag: GLuint) { 44 | gl::BufferData( 45 | gl::ARRAY_BUFFER, 46 | (vertices.len() * std::mem::size_of::()) as GLsizeiptr, 47 | vertices.as_ptr() as *const c_void, 48 | flag 49 | ); 50 | } 51 | } -------------------------------------------------------------------------------- /src/opengl/element_buffer.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use gl::types::*; 3 | use std::ffi::c_void; 4 | 5 | pub struct ElementBuffer { 6 | ebo: GLuint, 7 | attribute_index: u32, 8 | attribute_offset: usize 9 | } 10 | 11 | impl ElementBuffer { 12 | pub unsafe fn new() -> ElementBuffer { 13 | let mut ebo = 0; 14 | gl::GenBuffers(1, &mut ebo); 15 | ElementBuffer { ebo, attribute_index: 0, attribute_offset: 0 } 16 | } 17 | 18 | pub unsafe fn unbind() { 19 | gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, 0); 20 | } 21 | 22 | pub unsafe fn bind(&self) { 23 | gl::BindBuffer(gl::ELEMENT_ARRAY_BUFFER, self.ebo); 24 | } 25 | 26 | pub unsafe fn add_float_attribute(&mut self, length: usize, stride: usize) { 27 | gl::VertexAttribPointer( 28 | self.attribute_index, 29 | length as GLint, 30 | gl::FLOAT, 31 | gl::FALSE, 32 | (stride * std::mem::size_of::()) as GLsizei, 33 | if self.attribute_offset == 0 { 34 | std::ptr::null() 35 | } else { 36 | (self.attribute_offset * std::mem::size_of::()) as *const c_void 37 | } 38 | ); 39 | gl::EnableVertexAttribArray(self.attribute_index); 40 | self.attribute_index += 1; 41 | self.attribute_offset += length; 42 | } 43 | 44 | pub unsafe fn set_data(&self, indices: &Vec, flag: GLuint) { 45 | gl::BufferData( 46 | gl::ELEMENT_ARRAY_BUFFER, 47 | (indices.len() * std::mem::size_of::()) as GLsizeiptr, 48 | indices.as_ptr() as *const c_void, 49 | flag 50 | ); 51 | } 52 | } -------------------------------------------------------------------------------- /src/core/block_map.rs: -------------------------------------------------------------------------------- 1 | use super::block_type::BlockType; 2 | 3 | use super::chunk::{CHUNK_HEIGHT, CHUNK_SIZE}; 4 | 5 | // x by y by z 3-dimensional map using C arrays 6 | #[derive(Clone)] 7 | pub struct BlockMap { 8 | map: [[[BlockType; CHUNK_HEIGHT]; CHUNK_SIZE]; CHUNK_SIZE] 9 | } 10 | 11 | impl BlockMap { 12 | pub fn new() -> BlockMap { 13 | let map = [[[BlockType::Air; CHUNK_HEIGHT]; CHUNK_SIZE]; CHUNK_SIZE]; 14 | BlockMap { map } 15 | } 16 | 17 | pub fn get(&self, x: usize, y: usize, z: usize) -> BlockType { 18 | self.map[x][z][y] 19 | } 20 | 21 | pub fn highest_in_column(&self, x: usize, z: usize) -> usize { 22 | for i in 1..CHUNK_HEIGHT { 23 | let y = CHUNK_HEIGHT - i; 24 | if self.get(x, y, z) != BlockType::Air { 25 | return y 26 | } 27 | } 28 | 0 29 | } 30 | 31 | pub fn highest_in_column_from_y(&self, x: usize, height: usize, z: usize) -> usize { 32 | for i in 1..height + 1 { 33 | let y = (height - i).min(CHUNK_HEIGHT - 1); 34 | let block = self.get(x, y, z); 35 | if block != BlockType::Air && block != BlockType::Water { 36 | return y 37 | } 38 | } 39 | 0 40 | } 41 | 42 | pub fn set(&mut self, x: usize, y: usize, z: usize, block: BlockType) { 43 | if x >= CHUNK_SIZE { 44 | panic!(format!("Segfault, attempted to read map at invalid x: {}", x)) 45 | } 46 | 47 | if y >= CHUNK_HEIGHT { 48 | panic!(format!("Segfault, attempted to read map at invalid y: {}", y)) 49 | } 50 | 51 | if z >= CHUNK_SIZE { 52 | panic!(format!("Segfault, attempted to read map at invalid z: {}", z)) 53 | } 54 | 55 | self.map[x][z][y] = block; 56 | } 57 | } -------------------------------------------------------------------------------- /assets/shaders/voxal/fragment.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | out vec4 FragColor; 3 | 4 | in vec2 TexCoord; 5 | in vec3 Normal; 6 | in vec3 FragPos; 7 | in vec4 frag_pos_light_space; 8 | 9 | uniform sampler2D texture_map; 10 | uniform sampler2D shadow_map; 11 | 12 | uniform vec3 light_pos; 13 | uniform vec3 view_pos; 14 | uniform float time; 15 | 16 | /* 17 | float shadow_calculation(vec4 frag_pos_light_space) { 18 | // perform perspective divide 19 | vec3 proj_coords = frag_pos_light_space.xyz / frag_pos_light_space.w; 20 | 21 | // transform to 0-1 uv coord range for sampling 22 | proj_coords = proj_coords * 0.5 + 0.5; 23 | 24 | float closest_depth = texture(shadow_map, proj_coords.xy).r; 25 | float current_depth = proj_coords.z; 26 | 27 | // if fragment z is behind depth map z than it's in shadow 28 | float bias = 0.05; 29 | float shadow = (current_depth + bias) > closest_depth ? 1.0 : 0.0; 30 | return shadow; 31 | } 32 | */ 33 | 34 | void main() { 35 | vec4 color = texture(texture_map, vec2(TexCoord.x / 6.0, TexCoord.y / 6.0)); 36 | 37 | // blinn-phong lighting 38 | vec3 norm = normalize(Normal); 39 | vec3 light_color = vec3(1.0, 1.0, 1.0); 40 | 41 | // ambient 42 | float ambient_strength = 0.5; 43 | vec3 ambient = ambient_strength * light_color; 44 | 45 | // diffuse 46 | vec3 light_dir = vec3(-0.8, -1.0, 0.0); 47 | light_dir = normalize(-light_dir); 48 | float diff = max(dot(light_dir, norm), 0.0); 49 | vec3 diffuse = 0.8 * diff * light_color; 50 | 51 | // specular 52 | vec3 view_dir = normalize(view_pos - FragPos); 53 | vec3 halfway_dir = normalize(light_dir + view_dir); 54 | float spec = pow(max(dot(norm, halfway_dir), 0.0), 10); 55 | vec3 specular = spec * light_color; 56 | 57 | // shadow 58 | // float shadow = shadow_calculation(frag_pos_light_space); 59 | 60 | // calculate result by summing light sources 61 | vec3 lighting = (ambient + diffuse /*+ specular*/) * color.rgb; 62 | FragColor = vec4(lighting, color.a); 63 | } -------------------------------------------------------------------------------- /src/opengl/texture.rs: -------------------------------------------------------------------------------- 1 | use std::{ffi::c_void, path::Path}; 2 | 3 | use gl::types::*; 4 | use image::{ColorType, GenericImage}; 5 | 6 | #[derive(Clone, Copy)] 7 | pub struct Texture { 8 | id: u32, 9 | pub texture_id: GLenum 10 | } 11 | 12 | impl Texture { 13 | pub unsafe fn new(image_path: &str, texture_id: GLenum, flipped: bool) -> Texture { 14 | let mut id = 0; 15 | gl::GenTextures(1, &mut id); 16 | 17 | gl::ActiveTexture(texture_id); 18 | gl::BindTexture(gl::TEXTURE_2D, id); 19 | 20 | let mut img = image::open(&Path::new(image_path)) 21 | .expect("Failed to load texture"); 22 | if flipped { 23 | img = img.flipv(); 24 | } 25 | let data = img.raw_pixels(); 26 | 27 | let channels = match img.color() { 28 | ColorType::RGBA(_) => gl::RGBA, 29 | ColorType::RGB(_) => gl::RGB, 30 | _ => panic!(format!("Invalid image color type: {:?}", img.color())) 31 | }; 32 | 33 | // set texture wrapping and filtering options 34 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); 35 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); 36 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as i32); 37 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as i32); 38 | 39 | // bind image data 40 | gl::TexImage2D( 41 | gl::TEXTURE_2D, 42 | 0, 43 | channels as i32, 44 | img.width() as i32, 45 | img.height() as i32, 46 | 0, 47 | channels, 48 | gl::UNSIGNED_BYTE, 49 | &data[0] as *const u8 as *const c_void 50 | ); 51 | gl::GenerateMipmap(gl::TEXTURE_2D); 52 | Texture { 53 | id, 54 | texture_id 55 | } 56 | } 57 | 58 | pub unsafe fn bind(&self) { 59 | gl::ActiveTexture(self.texture_id); 60 | gl::BindTexture(gl::TEXTURE_2D, self.id); 61 | } 62 | } -------------------------------------------------------------------------------- /src/opengl/player_model.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{Deg, Matrix4, Vector3}; 2 | 3 | use super::{camera::Camera, cube::Cube, face_uvs::FaceUVs, texture::Texture}; 4 | 5 | pub struct PlayerModel { 6 | head: Cube, 7 | torso: Cube 8 | } 9 | 10 | impl PlayerModel { 11 | pub unsafe fn new(texture_path: &str) -> PlayerModel { 12 | let texture = Texture::new(texture_path, gl::TEXTURE0, true); 13 | let head = Cube::new( 14 | texture, 15 | FaceUVs::new(10.0, 10.0, 0.0, 0.0, 60.0, 20.0), 16 | FaceUVs::new(20.0, 10.0, 10.0, 0.0, 60.0, 20.0), 17 | FaceUVs::new(30.0, 10.0, 20.0, 0.0, 60.0, 20.0), 18 | FaceUVs::new(40.0, 10.0, 30.0, 0.0, 60.0, 20.0), 19 | FaceUVs::new(50.0, 10.0, 40.0, 0.0, 60.0, 20.0), 20 | FaceUVs::new(60.0, 10.0, 50.0, 0.0, 60.0, 20.0), 21 | ); 22 | let torso = Cube::new( 23 | texture, 24 | FaceUVs::new(10.0, 20.0, 0.0, 10.0, 60.0, 20.0), 25 | FaceUVs::new(20.0, 20.0, 10.0, 10.0, 60.0, 20.0), 26 | FaceUVs::new(30.0, 20.0, 20.0, 10.0, 60.0, 20.0), 27 | FaceUVs::new(40.0, 20.0, 30.0, 10.0, 60.0, 20.0), 28 | FaceUVs::new(50.0, 20.0, 40.0, 10.0, 60.0, 20.0), 29 | FaceUVs::new(60.0, 20.0, 50.0, 10.0, 60.0, 20.0), 30 | ); 31 | PlayerModel { head, torso } 32 | } 33 | 34 | pub unsafe fn draw(&self, camera: &Camera, position: Vector3, pitch: f32, yaw: f32) { 35 | let pitch_rotate = Matrix4::from_angle_x(Deg(pitch)); 36 | let yaw_rotate = Matrix4::from_angle_y(Deg(-yaw - 90.0)); 37 | self.head.draw( 38 | camera, 39 | Matrix4::from_translation(Vector3::new(position.x, position.y - 0.35, position.z)) 40 | * yaw_rotate 41 | * pitch_rotate 42 | * Matrix4::from_nonuniform_scale(0.7, 0.7, 0.7) 43 | ); 44 | self.torso.draw( 45 | camera, 46 | Matrix4::from_translation(Vector3::new(position.x, position.y - 1.1, position.z)) 47 | * yaw_rotate 48 | * Matrix4::from_nonuniform_scale(0.9, 0.7, 0.6) 49 | ); 50 | } 51 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rustycraft 2 | 3 | Simple Minecraft/Voxal Engine clone written in Rust and OpenGL 4 | 5 | 6 | 7 | 8 | 9 | ## Usage 10 | 11 | To run, simply clone the repo and run `cargo run --release`: 12 | ``` 13 | git clone https://github.com/profsucrose/rustycraft && cd rustycraft && cargo run --release 14 | ``` 15 | 16 | ## Local Worlds 17 | To play, you can do so locally by going to "Open World" and typing in a world name (which refers to the name of a world folder in the `game_data/worlds` directory) and then clicking "Open." 18 | 19 | You can move around with `WASD` and jump with `Space`. You can right-click to place a selected block at wherever your cursor is pointing and left-click to destroy any targeted block. You can use the `UpArrow` and `DownArrow` to cycle through the block options. 20 | 21 | The left `Super`/`Command`/`Windows` key can be used to unfocus or focus the window if the cursor is captured. `Esc` is used to exit the current world or server; world chunks are saved automatically whenever a block is placed or destroyed so there's no need to do so manually. 22 | 23 | The `F1` and `F3` keys are used for toggling the GUI/text and for toggling FPS/Fly camera modes respectively. 24 | 25 | ## Servers 26 | 27 | You can host a server from the corresponding repository [here](https://github.com/profsucrose/rustycraft-server)! 28 | 29 | To play on a server, go to the "Connect to Server" menu, type in the server address and then click "Connect." Assuming the specified server address is online you will proceed to join and receive chunk data. You can specify a port with `:`, but the default is 25566 (which is also the same when hosting a server). 30 | 31 | I myself am hosting a little RustyCraft server at `craft.profsucrose.dev`. Feel free to hop on if you want to try out this project's server functionality or potentially try to build something collectively! 32 | 33 | ### Chat 34 | The functionality/"gameplay" is of course identical to playing locally, however you can send messages in the server chat by pressing `T`, typing your message and pressing `Return` to send it. 35 | -------------------------------------------------------------------------------- /src/opengl/tex_quad.rs: -------------------------------------------------------------------------------- 1 | use gl::types::*; 2 | 3 | use super::{shader::Shader, texture::Texture, vertex_array::VertexArray, vertex_buffer::VertexBuffer}; 4 | 5 | pub struct TexQuad { 6 | texture: Texture, 7 | vao: VertexArray, 8 | vbo: VertexBuffer, 9 | shader: Shader, 10 | screen_width: u32, 11 | screen_height: u32 12 | } 13 | 14 | impl TexQuad { 15 | pub unsafe fn new(texture_path: &str, texture_id: GLuint, flipped: bool, screen_width: u32, screen_height: u32) -> TexQuad { 16 | let texture = Texture::new(texture_path, texture_id, flipped); 17 | let shader = Shader::new("assets/shaders/texquad/texquad_vertex.vert", "assets/shaders/texquad/texquad_fragment.frag"); 18 | let vao = VertexArray::new(); 19 | let mut vbo = VertexBuffer::new(); 20 | vao.bind(); 21 | vbo.bind(); 22 | vbo.add_float_attribute(2, 4); 23 | vbo.add_float_attribute(2, 4); 24 | VertexArray::unbind(); 25 | VertexBuffer::unbind(); 26 | 27 | TexQuad { texture, vao, vbo, shader, screen_width, screen_height } 28 | } 29 | 30 | pub unsafe fn draw(&self, left_x: f32, bottom_y: f32, right_x: f32, top_y: f32, alpha: f32) { 31 | self.vao.bind(); 32 | let vertices: Vec = vec![ 33 | // positions // uvs 34 | left_x, top_y, 0.0, 1.0, // top-left 35 | left_x, bottom_y, 0.0, 0.0, // bottom-left 36 | right_x, bottom_y, 1.0, 0.0, // bottom-right 37 | 38 | left_x, top_y, 0.0, 1.0, // top-left 39 | right_x, bottom_y, 1.0, 0.0, // bottom-right 40 | right_x, top_y, 1.0, 1.0 // top-right 41 | ]; 42 | self.vbo.bind(); 43 | self.vbo.set_data(&vertices, gl::DYNAMIC_DRAW); 44 | 45 | self.texture.bind(); 46 | self.shader.use_program(); 47 | self.shader.set_mat4("projection", cgmath::ortho(0.0, self.screen_width as f32, 0.0, self.screen_height as f32, -1.0, 100.0)); 48 | self.shader.set_texture("texture", &self.texture); 49 | self.shader.set_float("alpha", alpha); 50 | gl::DrawArrays(gl::TRIANGLES, 0, 6); 51 | } 52 | } -------------------------------------------------------------------------------- /src/core/block_type.rs: -------------------------------------------------------------------------------- 1 | use super::face::Face; 2 | use serde::{Serialize, Deserialize}; 3 | 4 | #[derive(Debug, PartialEq, Clone, Copy, Deserialize, Serialize)] 5 | pub enum BlockType { 6 | Grass = 0, 7 | Dirt, 8 | Log, 9 | Leaves, 10 | Stone, 11 | Air, 12 | Orange, 13 | Black, 14 | DarkOrange, 15 | Water, 16 | Sand, 17 | Snow, 18 | Cactus, 19 | StoneBrick, 20 | Plank, 21 | Brick 22 | } 23 | 24 | pub fn index_to_block(index: usize) -> Option { 25 | match index { 26 | 0 => Some(BlockType::Grass), 27 | 1 => Some(BlockType::Dirt), 28 | 2 => Some(BlockType::Log), 29 | 3 => Some(BlockType::Leaves), 30 | 4 => Some(BlockType::Stone), 31 | 5 => Some(BlockType::Air), 32 | 6 => Some(BlockType::Orange), 33 | 7 => Some(BlockType::Black), 34 | 8 => Some(BlockType::DarkOrange), 35 | 9 => Some(BlockType::Water), 36 | 10 => Some(BlockType::Sand), 37 | 11 => Some(BlockType::Snow), 38 | 12 => Some(BlockType::Cactus), 39 | 13 => Some(BlockType::StoneBrick), 40 | 14 => Some(BlockType::Plank), 41 | 15 => Some(BlockType::Brick), 42 | _ => None 43 | } 44 | } 45 | 46 | pub fn block_to_uv(block_face_type: BlockType, face: Face) -> f32 { 47 | match block_face_type { 48 | BlockType::Dirt => 0.0, 49 | BlockType::Grass => match face { 50 | Face::Top => 1.0, 51 | Face::Bottom => 0.0, 52 | _ => 6.0 53 | }, 54 | BlockType::Stone => 2.0, 55 | BlockType::Log => match face { 56 | Face::Top | Face::Bottom => 3.0, 57 | _ => 4.0 58 | }, 59 | BlockType::Leaves => 5.0, 60 | BlockType::Orange => 8.0, 61 | BlockType::DarkOrange => 9.0, 62 | BlockType::Black => 10.0, 63 | BlockType::Water => 11.0, 64 | BlockType::Sand => 12.0, 65 | BlockType::Snow => match face { 66 | Face::Top => 13.0, 67 | Face::Right | Face::Left | Face::Front | Face::Back => 14.0, 68 | Face::Bottom => 0.0 69 | }, 70 | BlockType::Cactus => 15.0, 71 | BlockType::StoneBrick => 16.0, 72 | BlockType::Plank => 17.0, 73 | BlockType::Brick => 18.0, 74 | BlockType::Air => panic!("Attempted to get block uv for BlockType::Air") 75 | } 76 | } -------------------------------------------------------------------------------- /src/opengl/depth_framebuffer.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::ptr; 3 | use gl::types::*; 4 | 5 | pub const SHADOW_WIDTH: GLint = 10024; 6 | pub const SHADOW_HEIGHT: GLint = 10024; 7 | 8 | // framebuffer for getting shadow map 9 | // for shadow calculations 10 | pub struct DepthFrameBuffer { 11 | id: GLuint, 12 | pub depth_map: GLuint 13 | } 14 | 15 | impl DepthFrameBuffer { 16 | // creates and binds framebuffer object 17 | pub unsafe fn new() -> DepthFrameBuffer { 18 | let mut id = 0; 19 | gl::GenFramebuffers(1, &mut id); 20 | 21 | // bind for attachment 22 | gl::BindFramebuffer(gl::FRAMEBUFFER, id); 23 | 24 | // depth map attachment 25 | let mut depth_map = 0; 26 | gl::GenTextures(1, &mut depth_map); 27 | gl::BindTexture(gl::TEXTURE_2D, depth_map); 28 | 29 | gl::TexImage2D(gl::TEXTURE_2D, 0, gl::DEPTH_COMPONENT as GLint, SHADOW_WIDTH, SHADOW_HEIGHT, 0, gl::DEPTH_COMPONENT, gl::FLOAT, ptr::null()); 30 | 31 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::NEAREST as GLint); 32 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::NEAREST as GLint); 33 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_BORDER as GLint); 34 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_BORDER as GLint); 35 | 36 | let border_color = [ 1.0, 1.0, 1.0, 1.0 ]; 37 | gl::TexParameterfv(gl::TEXTURE_2D, gl::TEXTURE_BORDER_COLOR, border_color.as_ptr()); 38 | 39 | // attach depth map 40 | gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::DEPTH_ATTACHMENT, gl::TEXTURE_2D, depth_map, 0); 41 | 42 | // tell OpenGL we're not going to attach any color buffer 43 | // as we only need depth 44 | gl::DrawBuffer(gl::NONE); 45 | gl::DrawBuffer(gl::NONE); 46 | 47 | if DepthFrameBuffer::is_complete() { 48 | println!("Depth framebuffer is complete!"); 49 | } 50 | 51 | DepthFrameBuffer::unbind(); 52 | 53 | DepthFrameBuffer { id, depth_map } 54 | } 55 | 56 | pub unsafe fn bind(&self) { 57 | gl::BindFramebuffer(gl::FRAMEBUFFER, self.id); 58 | } 59 | 60 | pub unsafe fn unbind() { 61 | gl::BindFramebuffer(gl::FRAMEBUFFER, 0); 62 | } 63 | 64 | pub unsafe fn delete(&self) { 65 | gl::DeleteFramebuffers(1, &self.id); 66 | } 67 | 68 | // framebuffer must be bound before checking completeness 69 | pub unsafe fn is_complete() -> bool { 70 | gl::CheckFramebufferStatus(gl::FRAMEBUFFER) == gl::FRAMEBUFFER_COMPLETE 71 | } 72 | } -------------------------------------------------------------------------------- /src/utils/chunk_utils.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::collections::HashSet; 3 | 4 | use crate::core::{block_map::BlockMap, block_type::index_to_block}; 5 | 6 | type BlocksInMesh = Vec<(usize, usize, usize)>; 7 | 8 | pub fn from_serialized(serialized: &String) -> (BlocksInMesh, BlockMap) { 9 | // format (127 as delimiter between layers) 10 | // 127 16x16 layer grid ... 11 | let mut blocks_in_mesh = Vec::new(); 12 | let mut blocks = BlockMap::new(); 13 | let bytes = serialized.as_bytes(); 14 | let mut i = 0; 15 | let mut y = 0; 16 | let mut iter_in_layer = 0; 17 | while i < bytes.len() { 18 | let byte = bytes[i]; 19 | if byte == 127 { 20 | y = bytes[i + 1] * 127 + bytes[i + 2]; 21 | iter_in_layer = 0; 22 | i += 2; 23 | } else { 24 | let x = iter_in_layer / 16; 25 | let z = iter_in_layer % 16; 26 | blocks_in_mesh.push((x, y as usize, z)); 27 | let block = index_to_block(byte as usize); 28 | blocks.set(x, y as usize, z, block.unwrap()); 29 | iter_in_layer += 1; 30 | } 31 | i += 1; 32 | } 33 | (blocks_in_mesh, blocks) 34 | } 35 | 36 | // for calculating compression ratio 37 | pub fn original_serialize(blocks_in_mesh: &BlocksInMesh, blocks: &BlockMap) -> String { 38 | let mut result = String::new(); 39 | for (x, y, z) in blocks_in_mesh.iter() { 40 | result.push(*x as u8 as char); 41 | result.push(*y as u8 as char); 42 | result.push(*z as u8 as char); 43 | result.push(blocks.get(*x, *y, *z) as u8 as char); 44 | } 45 | result 46 | } 47 | 48 | pub fn to_serialized(blocks_in_mesh: &BlocksInMesh, blocks: &BlockMap) -> String { 49 | // map y to list of x, z and block tuples 50 | let mut layer_ys = HashSet::new(); 51 | for (_, y, _) in blocks_in_mesh.iter() { 52 | layer_ys.insert(*y); 53 | } 54 | 55 | let mut serialized = String::new(); 56 | for y in layer_ys.iter() { 57 | // use 255 as delimiter, ignored in RLE compression 58 | serialized.push(127 as u8 as char); 59 | let y = *y as u8; 60 | // need two chars to represent 0-255 61 | let has_127 = if y > 127 { 1u8 } else { 0u8 }; 62 | serialized.push(has_127 as char); 63 | serialized.push((y % 127) as char); 64 | for x in 0..16 { 65 | for z in 0..16 { 66 | let block = blocks.get(x, y as usize, z); 67 | serialized.push(block as u8 as char); 68 | } 69 | } 70 | } 71 | serialized // run_length_encode(&serialized) 72 | } -------------------------------------------------------------------------------- /src/opengl/skybox.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use crate::opengl::{camera::Camera, shader::Shader, vertex_array::VertexArray, vertex_buffer::VertexBuffer}; 3 | 4 | pub struct SkyBox { 5 | vao: VertexArray, 6 | vbo: VertexBuffer, 7 | shader: Shader 8 | } 9 | 10 | impl SkyBox { 11 | pub unsafe fn new() -> SkyBox { 12 | let vao = VertexArray::new(); 13 | vao.bind(); 14 | 15 | let mut vbo = VertexBuffer::new(); 16 | let vertices = vec!( 17 | // front 18 | -0.5, -0.5, -0.5, 19 | 0.5, -0.5, -0.5, 20 | 0.5, 0.5, -0.5, 21 | 0.5, 0.5, -0.5, 22 | -0.5, 0.5, -0.5, 23 | -0.5, -0.5, -0.5, 24 | 25 | // back 26 | -0.5, -0.5, 0.5, 27 | 0.5, -0.5, 0.5, 28 | 0.5, 0.5, 0.5, 29 | 0.5, 0.5, 0.5, 30 | -0.5, 0.5, 0.5, 31 | -0.5, -0.5, 0.5, 32 | 33 | // left 34 | -0.5, 0.5, 0.5, 35 | -0.5, 0.5, -0.5, 36 | -0.5, -0.5, -0.5, 37 | -0.5, -0.5, -0.5, 38 | -0.5, -0.5, 0.5, 39 | -0.5, 0.5, 0.5, 40 | 41 | // right 42 | 0.5, 0.5, 0.5, 43 | 0.5, 0.5, -0.5, 44 | 0.5, -0.5, -0.5, 45 | 0.5, -0.5, -0.5, 46 | 0.5, -0.5, 0.5, 47 | 0.5, 0.5, 0.5, 48 | 49 | // bottom 50 | -0.5, -0.5, -0.5, 51 | 0.5, -0.5, -0.5, 52 | 0.5, -0.5, 0.5, 53 | 0.5, -0.5, 0.5, 54 | -0.5, -0.5, 0.5, 55 | -0.5, -0.5, -0.5, 56 | 57 | // top 58 | -0.5, 0.5, -0.5, 59 | 0.5, 0.5, -0.5, 60 | 0.5, 0.5, 0.5, 61 | 0.5, 0.5, 0.5, 62 | -0.5, 0.5, 0.5, 63 | -0.5, 0.5, -0.5, 64 | ); 65 | vbo.bind(); 66 | vbo.set_data(&vertices, gl::STATIC_DRAW); 67 | 68 | // position 69 | vbo.add_float_attribute(3, 3); 70 | 71 | VertexArray::unbind(); 72 | VertexBuffer::unbind(); 73 | 74 | let shader = Shader::new("assets/shaders/skybox/skybox_vertex.vert", "assets/shaders/skybox/skybox_fragment.frag"); 75 | SkyBox { vao, vbo, shader } 76 | } 77 | 78 | pub unsafe fn draw(&self, camera: &Camera) { 79 | gl::DepthMask(gl::FALSE); 80 | self.shader.use_program(); 81 | self.shader.set_mat4("view", camera.get_view()); 82 | self.shader.set_mat4("projection", camera.get_projection()); 83 | 84 | self.vao.bind(); 85 | self.vbo.bind(); 86 | gl::DrawArrays(gl::TRIANGLES, 0, 36); 87 | 88 | VertexArray::unbind(); 89 | VertexBuffer::unbind(); 90 | gl::DepthMask(gl::TRUE); 91 | } 92 | } -------------------------------------------------------------------------------- /src/opengl/button.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Vector3; 2 | 3 | use crate::opengl::text_renderer::TextJustification; 4 | 5 | use super::{tex_quad::TexQuad, text_renderer::TextRenderer}; 6 | 7 | pub struct Button { 8 | texquad: TexQuad, 9 | text: String, 10 | left_x: f32, 11 | right_x: f32, 12 | bottom_y: f32, 13 | top_y: f32, 14 | height: f32, 15 | screen_width: u32, 16 | screen_height: u32 17 | } 18 | 19 | impl Button { 20 | pub unsafe fn new(text: &str, x: f32, y: f32, width: f32, height: f32, screen_width: u32, screen_height: u32) -> Button { 21 | let texquad = TexQuad::new("assets/textures/button_wide.png", gl::TEXTURE0, true, screen_width, screen_height); 22 | let left_x = x - width / 2.0; 23 | let right_x = left_x + width; 24 | let bottom_y = y - height / 2.0; 25 | let top_y = bottom_y + height; 26 | Button { texquad, left_x, right_x, bottom_y, top_y, text: text.to_string(), height, screen_width, screen_height } 27 | } 28 | 29 | pub fn set_y(&mut self, y: f32) { 30 | self.bottom_y = y - self.height / 2.0; 31 | self.top_y = self.bottom_y + self.height; 32 | } 33 | 34 | pub fn is_hovered(&self, mouse_x: f32, mouse_y: f32, screen_width: u32, screen_height: u32) -> bool { 35 | let norm_mouse_x = mouse_x / screen_width as f32; 36 | let norm_mouse_y = mouse_y / screen_height as f32; 37 | norm_mouse_x > self.left_x / self.screen_width as f32 38 | && norm_mouse_x < self.right_x / self.screen_width as f32 39 | && norm_mouse_y > self.bottom_y / self.screen_height as f32 40 | && norm_mouse_y < self.top_y / self.screen_height as f32 41 | } 42 | 43 | pub unsafe fn draw(&self, text_renderer: &TextRenderer, mouse_x: f32, mouse_y: f32, screen_width: u32, screen_height: u32) { 44 | let text_x = self.left_x + ((self.right_x - self.left_x) / 2.0); 45 | let mut text_height = 0.0; 46 | for c in self.text.as_bytes() { 47 | let ch = text_renderer.get_char(*c as char); 48 | let height = ch.bearing.y as f32; 49 | if height > text_height { 50 | text_height = height; 51 | } 52 | } 53 | let text_y = self.bottom_y + ((self.top_y - self.bottom_y) / 2.0) - text_height / 2.0; 54 | 55 | // draw text 56 | text_renderer.render_text(self.text.as_str(), text_x, text_y, 1.0, Vector3::new(0.0, 0.0, 0.0), TextJustification::Center); 57 | 58 | // text shadow 59 | //text_renderer.render_text_centered(self.text.as_str(), text_x + 1.0, text_y - 1.0, 1.0, Vector3::new(165.0 / 255.0, 154.0 / 255.0, 154.0 / 255.0)); 60 | 61 | let opacity = if self.is_hovered(mouse_x, mouse_y, screen_width, screen_height) { 62 | 0.8 63 | } else { 64 | 1.0 65 | }; 66 | self.texquad.draw(self.left_x, self.bottom_y, self.right_x, self.top_y, opacity); 67 | } 68 | } -------------------------------------------------------------------------------- /src/opengl/cloud.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use cgmath::{Matrix4, Vector3}; 3 | use crate::opengl::{camera::Camera, shader::Shader, vertex_array::VertexArray, vertex_buffer::VertexBuffer}; 4 | 5 | pub struct Cloud { 6 | vao: VertexArray, 7 | vbo: VertexBuffer, 8 | shader: Shader 9 | } 10 | 11 | impl Cloud { 12 | pub unsafe fn new() -> Cloud { 13 | let vao = VertexArray::new(); 14 | vao.bind(); 15 | 16 | let mut vbo = VertexBuffer::new(); 17 | let vertices = vec!( 18 | // front 19 | -0.5, -0.5, -0.5, 20 | 0.5, -0.5, -0.5, 21 | 0.5, 0.5, -0.5, 22 | 0.5, 0.5, -0.5, 23 | -0.5, 0.5, -0.5, 24 | -0.5, -0.5, -0.5, 25 | 26 | // back 27 | -0.5, -0.5, 0.5, 28 | 0.5, -0.5, 0.5, 29 | 0.5, 0.5, 0.5, 30 | 0.5, 0.5, 0.5, 31 | -0.5, 0.5, 0.5, 32 | -0.5, -0.5, 0.5, 33 | 34 | // left 35 | -0.5, 0.5, 0.5, 36 | -0.5, 0.5, -0.5, 37 | -0.5, -0.5, -0.5, 38 | -0.5, -0.5, -0.5, 39 | -0.5, -0.5, 0.5, 40 | -0.5, 0.5, 0.5, 41 | 42 | // right 43 | 0.5, 0.5, 0.5, 44 | 0.5, 0.5, -0.5, 45 | 0.5, -0.5, -0.5, 46 | 0.5, -0.5, -0.5, 47 | 0.5, -0.5, 0.5, 48 | 0.5, 0.5, 0.5, 49 | 50 | // bottom 51 | -0.5, -0.5, -0.5, 52 | 0.5, -0.5, -0.5, 53 | 0.5, -0.5, 0.5, 54 | 0.5, -0.5, 0.5, 55 | -0.5, -0.5, 0.5, 56 | -0.5, -0.5, -0.5, 57 | 58 | // top 59 | -0.5, 0.5, -0.5, 60 | 0.5, 0.5, -0.5, 61 | 0.5, 0.5, 0.5, 62 | 0.5, 0.5, 0.5, 63 | -0.5, 0.5, 0.5, 64 | -0.5, 0.5, -0.5, 65 | ); 66 | vbo.bind(); 67 | vbo.set_data(&vertices, gl::STATIC_DRAW); 68 | 69 | // position 70 | vbo.add_float_attribute(3, 3); 71 | 72 | VertexArray::unbind(); 73 | VertexBuffer::unbind(); 74 | 75 | let shader = Shader::new("assets/shaders/cloud/cloud_vertex.vert", "assets/shaders/cloud/cloud_fragment.frag"); 76 | Cloud { vao, vbo, shader } 77 | } 78 | 79 | pub unsafe fn draw(&self, camera: &Camera, x: i32, y: i32, z: i32, z_offset: f32) { 80 | self.shader.use_program(); 81 | self.shader.set_mat4("view", camera.get_view()); 82 | self.shader.set_mat4("model", Matrix4::from_translation(Vector3::new(x as f32 * 10.0, y as f32, z as f32 * 10.0 + z_offset)) * Matrix4::from_nonuniform_scale(10.0, 2.0, 10.0)); 83 | self.shader.set_mat4("projection", camera.get_projection()); 84 | 85 | self.vao.bind(); 86 | self.vbo.bind(); 87 | gl::DrawArrays(gl::TRIANGLES, 0, 36); 88 | 89 | VertexArray::unbind(); 90 | VertexBuffer::unbind(); 91 | } 92 | } -------------------------------------------------------------------------------- /src/core/player.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Vector3; 2 | use crate::{core::block_type::BlockType, opengl::{camera::{Camera, CameraMode}}, traits::game_world::GameWorld}; 3 | 4 | const GRAVITY: f32 = -0.005; 5 | const TERMINAL_VEL: f32 = -0.6; 6 | 7 | pub struct Player { 8 | pub camera: Camera, 9 | is_jumping: bool, 10 | velocity_y: f32, 11 | camera_mode: CameraMode 12 | } 13 | 14 | impl Player { 15 | pub unsafe fn new(screen_width: u32, screen_height: u32) -> Player { 16 | let camera = Camera::new(screen_width, screen_height, 0.008); 17 | Player { 18 | camera, 19 | is_jumping: false, 20 | velocity_y: TERMINAL_VEL, 21 | camera_mode: CameraMode::FirstPerson 22 | } 23 | } 24 | 25 | pub fn toggle_camera(&mut self) { 26 | self.camera_mode = match self.camera_mode { 27 | CameraMode::FirstPerson => CameraMode::Free, 28 | CameraMode::Free => CameraMode::FirstPerson 29 | } 30 | } 31 | 32 | pub fn update_position(&mut self, world: &impl GameWorld, deltatime: f32) { 33 | let old_position = self.camera.position.clone(); 34 | self.camera.update_position(deltatime, self.camera_mode); 35 | if self.camera_mode == CameraMode::Free { 36 | return; 37 | } 38 | 39 | let sign = if self.camera.moving_backward { -1.0 } else { 1.0 }; 40 | let position = self.camera.position - Vector3::::new(0.0, 1.0, 0.0) + sign * self.camera.front / 4.0; 41 | let x = position.x.round() as i32; 42 | let y = position.y.round() as i32; 43 | let z = position.z.round() as i32; 44 | if !world.moveable(x, y, z) || !world.moveable(x, y + 1, z) { 45 | self.camera.position = old_position; 46 | } 47 | } 48 | 49 | pub fn update_alt(&mut self, world: &impl GameWorld) { 50 | if self.camera_mode == CameraMode::Free { 51 | return; 52 | } 53 | 54 | let x = self.camera.position.x.round() as i32; 55 | let y = (self.camera.position.y + 0.04).round() as i32; 56 | let z = self.camera.position.z.round() as i32; 57 | let ground_y = world.highest_in_column_from_y(x, y, z); 58 | if ground_y.is_none() { 59 | return; 60 | } 61 | 62 | let ground_y = ground_y.unwrap() + 2; 63 | let test_y = self.camera.position.y + 0.1 + self.velocity_y as f32; 64 | if world.moveable(x, test_y.round() as i32, z) { 65 | self.camera.position.y = test_y as f32; 66 | } else { 67 | self.velocity_y = TERMINAL_VEL; 68 | } 69 | 70 | if self.camera.position.y < (ground_y as f32) - 0.001 { 71 | self.is_jumping = false; 72 | self.camera.position.y = ground_y as f32; 73 | } 74 | self.velocity_y += GRAVITY; 75 | if self.velocity_y < TERMINAL_VEL { 76 | self.velocity_y = TERMINAL_VEL; 77 | } 78 | } 79 | 80 | pub fn underwater(&self, world: &impl GameWorld) -> bool { 81 | if self.camera_mode == CameraMode::Free { 82 | return false; 83 | } 84 | 85 | let player_x = self.camera.position.x.round() as i32; 86 | let player_y = self.camera.position.y.round() as i32; 87 | let player_z = self.camera.position.z.round() as i32; 88 | match world.get_block(player_x, player_y, player_z) { 89 | Some(block) => block == BlockType::Water, 90 | None => false 91 | } 92 | } 93 | 94 | pub fn jump(&mut self) { 95 | if !self.is_jumping { 96 | self.velocity_y = 0.05; 97 | self.is_jumping = true; 98 | } 99 | } 100 | } -------------------------------------------------------------------------------- /assets/shaders/voxal/geometry.geom: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | layout (points) in; 3 | layout (triangle_strip, max_vertices = 24) out; 4 | 5 | uniform mat4 model; 6 | uniform mat4 view; 7 | uniform mat4 projection; 8 | uniform mat4 light_space_matrix; 9 | 10 | out vec2 TexCoord; 11 | out vec3 Normal; 12 | out vec3 FragPos; 13 | // send frag position in light space 14 | // to fragment shader for shadow map 15 | // calculations 16 | out vec4 frag_pos_light_space; 17 | 18 | in VS_OUT { 19 | float[6] blockUVIndices; 20 | int facesToDraw; 21 | } gs_in[]; 22 | 23 | const vec4 cubeVerts[8] = vec4[8] ( 24 | vec4(-0.5, -0.5, -0.5, 1.0), // LB 0 25 | vec4(-0.5, 0.5, -0.5, 1.0), // LT 1 26 | vec4( 0.5, -0.5, -0.5, 1.0), // RB 2 27 | vec4( 0.5, 0.5, -0.5, 1.0), // RT 3 28 | 29 | vec4(-0.5, -0.5, 0.5, 1.0), // LB 4 30 | vec4(-0.5, 0.5, 0.5, 1.0), // LT 5 31 | vec4( 0.5, -0.5, 0.5, 1.0), // RB 6 32 | vec4( 0.5, 0.5, 0.5, 1.0) // RT 7 33 | ); 34 | 35 | const int cubeIndices[24] = int[24] ( 36 | 0, 1, 2, 3, // front 37 | 7, 6, 3, 2, // right 38 | 7, 5, 6, 4, // back 39 | 4, 0, 6, 2, // bottom 40 | 1, 0, 5, 4, // left 41 | 3, 1, 7, 5 // top 42 | ); 43 | 44 | const vec3 cubeNormals[6] = vec3[6] ( 45 | vec3( 0.0, 0.0, -1.0), // front 46 | vec3( 1.0, 0.0, 0.0), // right 47 | vec3( 0.0, 0.0, 1.0), // back 48 | vec3( 0.0, -1.0, 0.0), // bottom 49 | vec3(-1.0, 0.0, 0.0), // left 50 | vec3( 0.0, 1.0, 0.0) // top 51 | ); 52 | 53 | const vec2 cubeUVs[24] = vec2[24] ( 54 | // front 55 | vec2(0.0, 1.0), 56 | vec2(0.0, 0.0), 57 | vec2(1.0, 1.0), 58 | vec2(1.0, 0.0), 59 | 60 | // right 61 | vec2(1.0, 0.0), 62 | vec2(1.0, 1.0), 63 | vec2(0.0, 0.0), 64 | vec2(0.0, 1.0), 65 | 66 | // back 67 | vec2(0.0, 0.0), 68 | vec2(1.0, 0.0), 69 | vec2(0.0, 1.0), 70 | vec2(1.0, 1.0), 71 | 72 | // bottom 73 | vec2(1.0, 0.0), 74 | vec2(1.0, 1.0), 75 | vec2(0.0, 0.0), 76 | vec2(1.0, 0.0), 77 | 78 | // left 79 | vec2(1.0, 0.0), 80 | vec2(1.0, 1.0), 81 | vec2(0.0, 0.0), 82 | vec2(0.0, 1.0), 83 | 84 | // top 85 | vec2(1.0, 1.0), 86 | vec2(0.0, 1.0), 87 | vec2(1.0, 0.0), 88 | vec2(0.0, 0.0) 89 | ); 90 | 91 | void emit_vertex(vec4 local_position, vec2 local_uv, int index) { 92 | vec4 world_position = gl_in[0].gl_Position; 93 | vec3 position = (world_position + model * local_position).xyz; 94 | FragPos = position; 95 | gl_Position = projection * view * vec4(position, 1.0); 96 | float blockIndex = gs_in[0].blockUVIndices[index]; 97 | 98 | TexCoord = local_uv + vec2(float(int(blockIndex) % 6), float(int(blockIndex) / 6)); 99 | Normal = cubeNormals[index]; 100 | frag_pos_light_space = light_space_matrix * vec4(FragPos, 1.0); 101 | EmitVertex(); 102 | } 103 | 104 | void build_cube() { 105 | int faces = gs_in[0].facesToDraw; 106 | 107 | for (int i = 0; i < 6; i++) { 108 | // unpack bit to check if face is obfuscated 109 | // and should be drawn or not 110 | if ((faces >> (7 - i) & 1) != 1) { 111 | continue; 112 | } 113 | 114 | int indices_index = i * 4; 115 | emit_vertex(cubeVerts[cubeIndices[indices_index]], cubeUVs[indices_index], i); 116 | emit_vertex(cubeVerts[cubeIndices[indices_index + 1]], cubeUVs[indices_index + 1], i); 117 | emit_vertex(cubeVerts[cubeIndices[indices_index + 2]], cubeUVs[indices_index + 2], i); 118 | emit_vertex(cubeVerts[cubeIndices[indices_index + 3]], cubeUVs[indices_index + 3], i); 119 | 120 | EndPrimitive(); 121 | } 122 | } 123 | 124 | void main() { 125 | build_cube(); 126 | } -------------------------------------------------------------------------------- /src/opengl/framebuffer.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::ptr; 3 | use gl::types::*; 4 | 5 | use super::{shader::Shader, vertex_array::VertexArray, vertex_buffer::VertexBuffer}; 6 | 7 | pub struct FrameBuffer { 8 | id: GLuint, 9 | vao: VertexArray, 10 | vbo: VertexBuffer, 11 | shader: Shader, 12 | tex_color_buffer: GLuint 13 | } 14 | 15 | impl FrameBuffer { 16 | // creates and binds framebuffer object 17 | pub unsafe fn new(width: u32, height: u32) -> FrameBuffer { 18 | let mut id = 0; 19 | gl::GenFramebuffers(1, &mut id); 20 | 21 | // bind for attachment 22 | gl::BindFramebuffer(gl::FRAMEBUFFER, id); 23 | 24 | // texture color attachment 25 | let mut tex_color_buffer = 0; 26 | gl::GenTextures(1, &mut tex_color_buffer); 27 | gl::BindTexture(gl::TEXTURE_2D, tex_color_buffer); 28 | 29 | // allocates empty texture buffer so 30 | // make GL calls directly instead of using 31 | // abstracted class 32 | gl::TexImage2D(gl::TEXTURE_2D, 0, gl::RGB as GLint, width as GLint * 2, height as GLint * 2, 0, gl::RGB, gl::UNSIGNED_BYTE, ptr::null()); 33 | 34 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as GLint); 35 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as GLint); 36 | gl::FramebufferTexture2D(gl::FRAMEBUFFER, gl::COLOR_ATTACHMENT0, gl::TEXTURE_2D, tex_color_buffer, 0); 37 | 38 | // render buffer object for depth and stencil buffers 39 | let mut rbo = 0; 40 | gl::GenRenderbuffers(1, &mut rbo); 41 | gl::BindRenderbuffer(gl::RENDERBUFFER, rbo); 42 | gl::RenderbufferStorage(gl::RENDERBUFFER, gl::DEPTH24_STENCIL8, width as GLint * 2, height as GLint * 2); 43 | gl::FramebufferRenderbuffer(gl::FRAMEBUFFER, gl::DEPTH_STENCIL_ATTACHMENT, gl::RENDERBUFFER, rbo); 44 | 45 | if FrameBuffer::is_complete() { 46 | println!("Framebuffer is complete!"); 47 | } 48 | 49 | FrameBuffer::unbind(); 50 | 51 | let vao = VertexArray::new(); 52 | vao.bind(); 53 | 54 | let mut vbo = VertexBuffer::new(); 55 | vbo.bind(); 56 | 57 | vbo.add_float_attribute(2, 4); 58 | vbo.add_float_attribute(2, 4); 59 | 60 | let vertices: Vec = vec![ 61 | // positions // uvs 62 | -1.0, 1.0, 0.0, 1.0, 63 | -1.0, -1.0, 0.0, 0.0, 64 | 1.0, -1.0, 1.0, 0.0, 65 | 66 | -1.0, 1.0, 0.0, 1.0, 67 | 1.0, -1.0, 1.0, 0.0, 68 | 1.0, 1.0, 1.0, 1.0 69 | ]; 70 | 71 | vbo.set_data(&vertices, gl::STATIC_DRAW); 72 | 73 | let shader = Shader::new("assets/shaders/framebuffer/fb_vertex.vert", "assets/shaders/framebuffer/fb_fragment.frag"); 74 | 75 | VertexArray::unbind(); 76 | VertexBuffer::unbind(); 77 | 78 | FrameBuffer { id, vao, vbo, shader, tex_color_buffer } 79 | } 80 | 81 | pub unsafe fn draw(&self) { 82 | // bind to 0th framebuffer to draw 83 | FrameBuffer::unbind(); 84 | gl::Disable(gl::DEPTH_TEST); 85 | gl::ClearColor(1.0, 1.0, 1.0, 1.0); 86 | gl::Clear(gl::COLOR_BUFFER_BIT); 87 | 88 | self.shader.use_program(); 89 | self.vao.bind(); 90 | self.vbo.bind(); 91 | gl::BindTexture(gl::TEXTURE_2D, self.tex_color_buffer); 92 | gl::DrawArrays(gl::TRIANGLES, 0, 6); 93 | } 94 | 95 | pub unsafe fn bind(&self) { 96 | gl::BindFramebuffer(gl::FRAMEBUFFER, self.id); 97 | } 98 | 99 | pub unsafe fn unbind() { 100 | gl::BindFramebuffer(gl::FRAMEBUFFER, 0); 101 | } 102 | 103 | pub unsafe fn delete(&self) { 104 | gl::DeleteFramebuffers(1, &self.id); 105 | } 106 | 107 | // framebuffer must be bound before checking completeness 108 | pub unsafe fn is_complete() -> bool { 109 | gl::CheckFramebufferStatus(gl::FRAMEBUFFER) == gl::FRAMEBUFFER_COMPLETE 110 | } 111 | } -------------------------------------------------------------------------------- /src/opengl/camera.rs: -------------------------------------------------------------------------------- 1 | use cgmath::{Deg, InnerSpace, Matrix4, Point3, Vector3, perspective, vec3}; 2 | use glfw::{Action, Key}; 3 | 4 | use crate::utils::vector_utils::get_direction_from_mouse_move; 5 | 6 | #[derive(PartialEq, Clone, Copy)] 7 | pub enum CameraMode { 8 | FirstPerson, 9 | Free 10 | } 11 | 12 | pub struct Camera { 13 | pub position: Vector3, 14 | pub front: Vector3, 15 | pub up: Vector3, 16 | pub moving_forward: bool, 17 | pub moving_backward: bool, 18 | pub moving_right: bool, 19 | pub moving_left: bool, 20 | pub yaw: f32, 21 | pub pitch: f32, 22 | pub fov: f32, 23 | pub screen_width: u32, 24 | pub screen_height: u32, 25 | pub speed: f32 26 | } 27 | 28 | impl Camera { 29 | pub fn new(screen_width: u32, screen_height: u32, speed: f32) -> Camera { 30 | Camera { 31 | position: vec3(0.0, 0.0, 0.0), 32 | front: vec3(0.0, 0.0, -1.0), 33 | up: vec3(0.0, 1.0, 0.0), 34 | moving_forward: false, 35 | moving_backward: false, 36 | moving_right: false, 37 | moving_left: false, 38 | pitch: 0.0, 39 | yaw: -90.0, 40 | fov: 45.0, 41 | screen_width, 42 | screen_height, 43 | speed 44 | } 45 | } 46 | 47 | pub fn process_keyboard(&mut self, key: Key, action: Action) { 48 | let pressed = match action { 49 | Action::Press => true, 50 | Action::Release => false, 51 | _ => return 52 | }; 53 | match key { 54 | Key::W => self.moving_forward = pressed, 55 | Key::A => self.moving_left = pressed, 56 | Key::S => self.moving_backward = pressed, 57 | Key::D => self.moving_right = pressed, 58 | _ => () 59 | } 60 | } 61 | 62 | pub fn update_position(&mut self, deltatime: f32, mode: CameraMode) { 63 | let speed = self.speed * deltatime; 64 | let old_y = self.position.y; 65 | 66 | let mut front = self.front; 67 | if mode == CameraMode::FirstPerson { 68 | front = Vector3::new(self.front.x, 0.0, self.front.z).normalize(); 69 | } 70 | if self.moving_forward { 71 | self.position += speed * front 72 | } 73 | 74 | if self.moving_backward { 75 | self.position -= speed * front 76 | } 77 | 78 | if self.moving_right { 79 | self.position += speed * self.front.cross(self.up).normalize() 80 | } 81 | 82 | if self.moving_left { 83 | self.position -= speed * self.front.cross(self.up).normalize() 84 | } 85 | 86 | if mode == CameraMode::FirstPerson { 87 | self.position.y = old_y; 88 | } 89 | } 90 | 91 | pub fn mouse_callback(&mut self, x_offset: f32, y_offset: f32) { 92 | let (yaw, pitch, direction) = get_direction_from_mouse_move(0.3, self.yaw, self.pitch, x_offset, y_offset); 93 | self.pitch = pitch; 94 | self.yaw = yaw; 95 | self.front = direction; 96 | } 97 | 98 | pub fn scroll_callback(&mut self, y_offset: f32) { 99 | self.fov -= y_offset; 100 | // clamp fov 101 | if self.fov < 1.0 { 102 | self.fov = 1.0 103 | } else if self.fov > 150.0 { 104 | self.fov = 150.0 105 | } 106 | } 107 | 108 | pub fn get_projection(&self) -> Matrix4 { 109 | perspective(Deg(self.fov), (self.screen_width as f32) / (self.screen_height as f32), 0.01, 1000.0) 110 | } 111 | 112 | pub fn get_view(&self) -> Matrix4 { 113 | Matrix4::look_at( 114 | Point3::new( 115 | self.position.x, 116 | self.position.y, 117 | self.position.z 118 | ), 119 | Point3::new( 120 | self.position.x + self.front.x, 121 | self.position.y + self.front.y, 122 | self.position.z + self.front.z 123 | ), 124 | self.up 125 | ) 126 | } 127 | } -------------------------------------------------------------------------------- /src/opengl/cube.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use cgmath::Matrix4; 3 | 4 | use crate::opengl::camera::Camera; 5 | 6 | use super::{face_uvs::FaceUVs, shader::Shader, texture::Texture, vertex_array::VertexArray, vertex_buffer::VertexBuffer}; 7 | 8 | pub struct Cube { 9 | vao: VertexArray, 10 | vbo: VertexBuffer, 11 | shader: Shader, 12 | texture: Texture 13 | } 14 | 15 | impl Cube { 16 | pub unsafe fn new( 17 | texture: Texture, 18 | face_front: FaceUVs, 19 | face_back: FaceUVs, 20 | face_left: FaceUVs, 21 | face_right: FaceUVs, 22 | face_bottom: FaceUVs, 23 | face_top: FaceUVs 24 | ) -> Cube { 25 | let vao = VertexArray::new(); 26 | vao.bind(); 27 | 28 | let mut vbo = VertexBuffer::new(); 29 | let vertices = vec!( 30 | // front 31 | -0.5, -0.5, -0.5, face_front.left, face_front.bottom, 32 | 0.5, -0.5, -0.5, face_front.right, face_front.bottom, 33 | 0.5, 0.5, -0.5, face_front.right, face_front.top, 34 | 0.5, 0.5, -0.5, face_front.right, face_front.top, 35 | -0.5, 0.5, -0.5, face_front.left, face_front.top, 36 | -0.5, -0.5, -0.5, face_front.left, face_front.bottom, 37 | 38 | // back 39 | -0.5, -0.5, 0.5, face_back.left, face_back.bottom, 40 | 0.5, -0.5, 0.5, face_back.right, face_back.bottom, 41 | 0.5, 0.5, 0.5, face_back.right, face_back.top, 42 | 0.5, 0.5, 0.5, face_back.right, face_back.top, 43 | -0.5, 0.5, 0.5, face_back.left, face_back.top, 44 | -0.5, -0.5, 0.5, face_back.left, face_back.bottom, 45 | 46 | // left 47 | -0.5, 0.5, 0.5, face_left.left, face_back.top, 48 | -0.5, 0.5, -0.5, face_left.right, face_back.top, 49 | -0.5, -0.5, -0.5, face_left.right, face_back.bottom, 50 | -0.5, -0.5, -0.5, face_left.right, face_back.bottom, 51 | -0.5, -0.5, 0.5, face_left.left, face_back.bottom, 52 | -0.5, 0.5, 0.5, face_left.left, face_back.top, 53 | 54 | // right 55 | 0.5, 0.5, 0.5, face_right.left, face_right.top, 56 | 0.5, 0.5, -0.5, face_right.right, face_right.top, 57 | 0.5, -0.5, -0.5, face_right.right, face_right.bottom, 58 | 0.5, -0.5, -0.5, face_right.right, face_right.bottom, 59 | 0.5, -0.5, 0.5, face_right.left, face_right.bottom, 60 | 0.5, 0.5, 0.5, face_right.left, face_right.top, 61 | 62 | // bottom 63 | -0.5, -0.5, -0.5, face_bottom.left, face_bottom.bottom, 64 | 0.5, -0.5, -0.5, face_bottom.right, face_bottom.bottom, 65 | 0.5, -0.5, 0.5, face_bottom.right, face_bottom.top, 66 | 0.5, -0.5, 0.5, face_bottom.right, face_bottom.top, 67 | -0.5, -0.5, 0.5, face_bottom.left, face_bottom.top, 68 | -0.5, -0.5, -0.5, face_bottom.left, face_bottom.bottom, 69 | 70 | // top 71 | -0.5, 0.5, -0.5, face_top.left, face_top.bottom, 72 | 0.5, 0.5, -0.5, face_top.right, face_top.bottom, 73 | 0.5, 0.5, 0.5, face_top.right, face_top.top, 74 | 0.5, 0.5, 0.5, face_top.right, face_top.top, 75 | -0.5, 0.5, 0.5, face_top.left, face_top.top, 76 | -0.5, 0.5, -0.5, face_top.left, face_top.bottom 77 | ); 78 | vbo.bind(); 79 | vbo.set_data(&vertices, gl::STATIC_DRAW); 80 | 81 | // position 82 | vbo.add_float_attribute(3, 5); 83 | 84 | // texcoords 85 | vbo.add_float_attribute(2, 5); 86 | 87 | VertexArray::unbind(); 88 | VertexBuffer::unbind(); 89 | 90 | let shader = Shader::new("assets/shaders/player/player_vertex.vert", "assets/shaders/player/player_fragment.frag"); 91 | Cube { vao, vbo, shader, texture } 92 | } 93 | 94 | pub unsafe fn draw(&self, camera: &Camera, model: Matrix4) { 95 | self.texture.bind(); 96 | 97 | self.shader.use_program(); 98 | self.shader.set_mat4("model", model); 99 | self.shader.set_mat4("view", camera.get_view()); 100 | self.shader.set_mat4("projection", camera.get_projection()); 101 | self.shader.set_texture("player_texture", &self.texture); 102 | 103 | self.vao.bind(); 104 | self.vbo.bind(); 105 | gl::DrawArrays(gl::TRIANGLES, 0, 36); 106 | 107 | VertexArray::unbind(); 108 | VertexBuffer::unbind(); 109 | } 110 | } -------------------------------------------------------------------------------- /src/opengl/input.rs: -------------------------------------------------------------------------------- 1 | use cgmath::Vector3; 2 | use glfw::Key; 3 | 4 | use super::{tex_quad::TexQuad, text_renderer::{TextJustification, TextRenderer}}; 5 | 6 | pub struct Input { 7 | texquad: TexQuad, 8 | pub text: String, 9 | width: f32, 10 | left_x: f32, 11 | right_x: f32, 12 | bottom_y: f32, 13 | top_y: f32, 14 | focused: bool, 15 | scale: f32, 16 | justification: TextJustification 17 | } 18 | 19 | impl Input { 20 | pub unsafe fn new(x: f32, y: f32, width: f32, height: f32, screen_width: u32, screen_height: u32, scale: f32, justification: TextJustification) -> Input { 21 | let texquad = TexQuad::new("assets/textures/input.png", gl::TEXTURE0, true, screen_width, screen_height); 22 | let left_x = x - width / 2.0; 23 | let right_x = left_x + width; 24 | let bottom_y = y - height / 2.0; 25 | let top_y = bottom_y + height; 26 | Input { texquad, text: String::new(), width, left_x, right_x, bottom_y, top_y, focused: false, scale, justification } 27 | } 28 | 29 | pub fn update_focus(&mut self, mouse_x: f32, mouse_y: f32) { 30 | self.focused = mouse_x > self.left_x && mouse_x < self.right_x && mouse_y > self.bottom_y && mouse_y < self.top_y; 31 | } 32 | 33 | pub fn set_focus(&mut self, focused: bool) { 34 | self.focused = focused; 35 | } 36 | 37 | pub fn type_key(&mut self, key: Key, shift: bool, text_renderer: &TextRenderer) { 38 | if !self.focused { 39 | return; 40 | } 41 | 42 | if key == Key::Backspace { 43 | if self.text.len() > 0 { 44 | self.text.remove(self.text.len() - 1); 45 | } 46 | return; 47 | } 48 | 49 | // return if key is invalid or if text has reached input bounds 50 | if text_renderer.calc_width(self.text.as_str(), self.scale) > self.width - 30.0 || key as usize > Key::Z as usize { 51 | return; 52 | } 53 | 54 | let ch_id = (key as usize - Key::A as usize) + 65; 55 | let ch = if shift { 56 | // map characters to uppercase ASCII 57 | let ch_id = match ch_id as u8 { 58 | b';' => b':', 59 | b'1' => b'!', 60 | b'2' => b'@', 61 | b'3' => b'#', 62 | b'4' => b'$', 63 | b'5' => b'%', 64 | b'6' => b'^', 65 | b'7' => b'&', 66 | b'8' => b'*', 67 | b'9' => b'(', 68 | b'0' => b')', 69 | b'/' => b'?', 70 | b'=' => b'+', 71 | b'\'' => b'"', 72 | b'[' => b'{', 73 | b']' => b'}', 74 | b'\\' => b'|', 75 | b'-' => b'_', 76 | b'`' => b'~', 77 | b',' => b'<', 78 | b'.' => b'>', 79 | _ => ch_id as u8 80 | }; 81 | let ch_id = if ch_id == 59 { 82 | 58 83 | } else { 84 | ch_id 85 | }; 86 | (ch_id as u8 as char).to_uppercase().nth(0).unwrap() 87 | } else { 88 | (ch_id as u8 as char).to_lowercase().nth(0).unwrap() 89 | }; 90 | 91 | self.text.push_str(ch.to_string().as_str()); 92 | } 93 | 94 | pub unsafe fn draw(&self, text_renderer: &TextRenderer, ) { 95 | let text_x = match self.justification { 96 | TextJustification::Center => self.left_x + ((self.right_x - self.left_x) / 2.0), 97 | TextJustification::Left => self.left_x + 20.0 98 | }; 99 | let text_height = text_renderer.get_char('d' as char).bearing.y as f32; 100 | let text_y = self.bottom_y + ((self.top_y - self.bottom_y) / 2.0) - text_height / 2.0; 101 | 102 | // draw text 103 | text_renderer.render_text(self.text.as_str(), text_x, text_y, self.scale, Vector3::new(1.0, 1.0, 1.0), self.justification); 104 | 105 | if self.focused { 106 | let width = text_renderer.calc_width(self.text.as_str(), self.scale); 107 | let mut underscore_x = match self.justification { 108 | TextJustification::Left => text_x + width, 109 | TextJustification::Center => text_x + width / 2.0, 110 | }; 111 | if self.text.len() > 0 { 112 | underscore_x += 1.0; 113 | } 114 | text_renderer.render_text("_", underscore_x, text_y, self.scale, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 115 | } 116 | 117 | self.texquad.draw(self.left_x, self.bottom_y, self.right_x, self.top_y, 1.0); 118 | } 119 | } -------------------------------------------------------------------------------- /src/multiplayer/server_chunk.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::sync::Arc; 3 | use crate::{core::{block_map::BlockMap, block_type::{BlockType, block_to_uv}, face::Face}, traits::game_chunk::GameChunk, utils::chunk_utils::from_serialized}; 4 | 5 | #[derive(Clone)] 6 | pub struct ServerChunk { 7 | blocks: BlockMap, 8 | blocks_in_mesh: Vec<(usize, usize, usize)>, 9 | x: i32, 10 | z: i32, 11 | pub mesh: Arc<(Vec, Vec)> // cache mesh (Arc to be thread-safe) 12 | } 13 | 14 | impl GameChunk for ServerChunk { 15 | fn get_blocks(&self) -> &BlockMap { 16 | &self.blocks 17 | } 18 | } 19 | 20 | impl ServerChunk { 21 | pub fn from_serialized(chunk_data: String, x: i32, z: i32) -> ServerChunk { 22 | // follows same format as chunk data files 23 | // [x] [y] [z] [block_index] 24 | // [x1] [y1] [z1] [block_index1] 25 | // ... 26 | let (blocks_in_mesh, blocks) = from_serialized(&chunk_data); 27 | ServerChunk { blocks, blocks_in_mesh, x: x * 16, z: z * 16, mesh: Arc::new((vec![], vec![])) } 28 | } 29 | 30 | pub fn gen_mesh(&self, right_chunk: &ServerChunk, left_chunk: &ServerChunk, front_chunk: &ServerChunk, back_chunk: &ServerChunk) -> Arc<(Vec, Vec)> { 31 | let mut vertices = Vec::new(); 32 | // water is transparent so is in separate 33 | // vector to draw after opaque blocks 34 | let mut water_vertices = Vec::new(); 35 | for (x, y, z) in self.blocks_in_mesh.iter() { 36 | // let instant = std::time::Instant::now(); 37 | let x = *x; 38 | let y = *y; 39 | let z = *z; 40 | 41 | let block = self.blocks.get(x, y, z); 42 | if block == BlockType::Air { 43 | continue; 44 | } 45 | 46 | let x = x as i32; 47 | let y = y as i32; 48 | let z = z as i32; 49 | let faces = 50 | 0 51 | | if self.can_place_mesh_face_at_block(x, y, z - 1, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b10000000 } else { 0 } 52 | | if self.can_place_mesh_face_at_block(x + 1, y, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b01000000 } else { 0 } 53 | | if self.can_place_mesh_face_at_block(x, y, z + 1, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00100000 } else { 0 } 54 | | if self.can_place_mesh_face_at_block(x, y - 1, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00010000 } else { 0 } 55 | | if self.can_place_mesh_face_at_block(x - 1, y, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00001000 } else { 0 } 56 | | if self.can_place_mesh_face_at_block(x, y + 1, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00000100 } else { 0 }; 57 | 58 | 59 | if faces == 0 { 60 | continue; 61 | } 62 | 63 | let world_x = (x + self.x) as f32; 64 | let world_y = y as f32; 65 | let world_z = (z + self.z) as f32; 66 | 67 | let vertices_to_push_to = if block == BlockType::Water { 68 | &mut water_vertices 69 | }else { 70 | &mut vertices 71 | }; 72 | vertices_to_push_to.push(world_x); 73 | vertices_to_push_to.push(world_y); 74 | vertices_to_push_to.push(world_z); 75 | 76 | for i in 0..6 { 77 | let face = match i { 78 | 0 => Face::Front, 79 | 1 => Face::Right, 80 | 2 => Face::Back, 81 | 3 => Face::Bottom, 82 | 4 => Face::Left, 83 | 5 => Face::Top, 84 | _ => panic!("Attempted to convert invalid index to face when setting vertex texture UV indices") 85 | }; 86 | vertices_to_push_to.push(block_to_uv(block, face)); 87 | } 88 | 89 | vertices_to_push_to.push(faces as f32); 90 | // println!("Took {:?} to add a block", instant.elapsed()); 91 | } 92 | //self.mesh = Rc::new(vertices); 93 | 94 | Arc::new((vertices, water_vertices)) 95 | } 96 | 97 | pub fn block_at(&self, x: usize, y: usize, z: usize) -> BlockType { 98 | self.blocks.get(x, y, z) 99 | } 100 | 101 | pub fn set_block(&mut self, x: usize, y: usize, z: usize, block: BlockType) { 102 | self.blocks.set(x, y, z, block); 103 | if block == BlockType::Air { 104 | for i in 0..self.blocks_in_mesh.len() - 1 { 105 | if self.blocks_in_mesh[i] == (x, y, z) { 106 | self.blocks_in_mesh.remove(i); 107 | break; 108 | } 109 | } 110 | } else { 111 | self.blocks_in_mesh.push((x, y, z)); 112 | } 113 | } 114 | 115 | pub fn can_place_at_local_spot(&self, x: i32, y: i32, z: i32, block: BlockType) -> bool { 116 | if y < 0 { 117 | return false 118 | } 119 | 120 | let block_spot = self.blocks.get(x as usize, y as usize, z as usize); 121 | block_spot == BlockType::Air || (block != BlockType::Water && block_spot == BlockType::Water) 122 | } 123 | 124 | pub fn can_place_mesh_face_at_block(&self, x: i32, y: i32, z: i32, block: BlockType, right_chunk: &ServerChunk, left_chunk: &ServerChunk, front_chunk: &ServerChunk, back_chunk: &ServerChunk) -> bool { 125 | if y < 0 { 126 | return false 127 | } 128 | 129 | // if outside own chunk fetch edge 130 | // of respective adjacent chunk 131 | if x == 16 { 132 | return right_chunk.can_place_at_local_spot(0, y, z, block); 133 | } else if x == -1 { 134 | return left_chunk.can_place_at_local_spot(15, y, z, block); 135 | } else if z == 16 { 136 | return front_chunk.can_place_at_local_spot(x, y, 0, block); 137 | } else if z == -1 { 138 | return back_chunk.can_place_at_local_spot(x, y, 15, block); 139 | } 140 | 141 | let block_spot = self.blocks.get(x as usize, y as usize, z as usize); 142 | block_spot == BlockType::Air || (block_spot == BlockType::Water && block != BlockType::Water) 143 | } 144 | 145 | pub fn highest_in_column(&self, x: usize, z: usize) -> usize { 146 | self.blocks.highest_in_column(x, z) 147 | } 148 | 149 | pub fn highest_in_column_from_y(&self, x: usize, y: usize, z: usize) -> usize { 150 | self.blocks.highest_in_column_from_y(x, y, z) 151 | } 152 | } -------------------------------------------------------------------------------- /src/opengl/shader.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::fs; 3 | use std::ffi::CString; 4 | use cgmath::{Matrix, Matrix4, Vector3}; 5 | use gl::types::*; 6 | use std::ptr; 7 | use std::str; 8 | 9 | use super::texture::Texture; 10 | 11 | pub struct Shader { 12 | pub id: u32 13 | } 14 | 15 | impl Shader { 16 | pub unsafe fn new(vertex_path: &str, fragment_path: &str) -> Shader { 17 | let (vertex_shader, fragment_shader) = Shader::gen_shader_program_with_vert_and_frag(vertex_path, fragment_path); 18 | 19 | // shader program 20 | let id = gl::CreateProgram(); 21 | gl::AttachShader(id, vertex_shader); 22 | gl::AttachShader(id, fragment_shader); 23 | Shader::link_shader_program(id); 24 | 25 | // delete shaders as they're no longer needed 26 | gl::DeleteShader(vertex_shader); 27 | gl::DeleteShader(fragment_shader); 28 | 29 | Shader { id } 30 | } 31 | 32 | pub unsafe fn new_with_geom(vertex_path: &str, fragment_path: &str, geometry_path: &str) -> Shader { 33 | let (vertex_shader, fragment_shader) = Shader::gen_shader_program_with_vert_and_frag(vertex_path, fragment_path); 34 | let geometry_source = fs::read_to_string(geometry_path) 35 | .expect(format!("Could not read vertex shader at path {}", geometry_path).as_str()); 36 | let geometry_source_cstring = CString::new(geometry_source.as_bytes()) 37 | .expect("Could not convert vertex shader source to CString"); 38 | 39 | // compile geometry shader and check for errors 40 | let geometry_shader = gl::CreateShader(gl::GEOMETRY_SHADER); 41 | gl::ShaderSource(geometry_shader, 1, &CString::new(geometry_source_cstring.as_bytes()).unwrap().as_ptr(), ptr::null()); 42 | gl::CompileShader(geometry_shader); 43 | 44 | let mut success = gl::FALSE as GLint; 45 | let mut info_log = Vec::with_capacity(512); 46 | info_log.set_len(512 - 1); // subtract 1 to skip trailing null char 47 | gl::GetShaderiv(geometry_shader, gl::COMPILE_STATUS, &mut success); 48 | if success != gl::TRUE as GLint { 49 | gl::GetShaderInfoLog(geometry_shader, 512, ptr::null_mut(), info_log.as_mut_ptr() as *mut GLchar); 50 | println!("GEOMETRY SHADER COMPILATION FAILED\n{:?}", stringify_vec_u8(info_log)); 51 | } 52 | 53 | // shader program 54 | let id = gl::CreateProgram(); 55 | gl::AttachShader(id, vertex_shader); 56 | gl::AttachShader(id, fragment_shader); 57 | gl::AttachShader(id, geometry_shader); 58 | Shader::link_shader_program(id); 59 | 60 | // delete shaders as they're no longer needed 61 | gl::DeleteShader(vertex_shader); 62 | gl::DeleteShader(fragment_shader); 63 | gl::DeleteShader(geometry_shader); 64 | 65 | Shader { id } 66 | } 67 | 68 | unsafe fn link_shader_program(id: GLuint) { 69 | gl::LinkProgram(id); 70 | 71 | let mut success = gl::FALSE as GLint; 72 | let mut info_log = Vec::with_capacity(512); 73 | info_log.set_len(512 - 1); // subtract 1 to skip trailing null char 74 | gl::GetProgramiv(id, gl::LINK_STATUS, &mut success); 75 | if success != gl::TRUE as GLint { 76 | gl::GetShaderInfoLog(id, 512, ptr::null_mut(), info_log.as_mut_ptr() as *mut GLchar); 77 | println!("SHADER PROGRAM LINKING FAILED\n{:?}", stringify_vec_u8(info_log)); 78 | } 79 | } 80 | 81 | unsafe fn gen_shader_program_with_vert_and_frag(vertex_path: &str, fragment_path: &str) -> (GLuint, GLuint) { 82 | let vertex_source = fs::read_to_string(vertex_path) 83 | .expect(format!("Could not read vertex shader at path {}", vertex_path).as_str()); 84 | let fragment_source = fs::read_to_string(fragment_path) 85 | .expect(format!("Could not read fragment shader at path {}", fragment_path).as_str()); 86 | let vertex_source_cstring = CString::new(vertex_source.as_bytes()) 87 | .expect("Could not convert vertex shader source to CString"); 88 | let fragment_source_cstring = CString::new(fragment_source.as_bytes()) 89 | .expect("Could not convert fragment shader source to CString"); 90 | 91 | // compile vertex shader and check for errors 92 | let vertex_shader = gl::CreateShader(gl::VERTEX_SHADER); 93 | gl::ShaderSource(vertex_shader, 1, &CString::new(vertex_source_cstring.as_bytes()).unwrap().as_ptr(), ptr::null()); 94 | gl::CompileShader(vertex_shader); 95 | 96 | let mut success = gl::FALSE as GLint; 97 | let mut info_log = Vec::with_capacity(512); 98 | info_log.set_len(512 - 1); // subtract 1 to skip trailing null char 99 | gl::GetShaderiv(vertex_shader, gl::COMPILE_STATUS, &mut success); 100 | if success != gl::TRUE as GLint { 101 | gl::GetShaderInfoLog(vertex_shader, 512, ptr::null_mut(), info_log.as_mut_ptr() as *mut GLchar); 102 | println!("VERTEX SHADER COMPILATION FAILED\n{:?}", stringify_vec_u8(info_log)); 103 | } 104 | 105 | // compile fragment shader and check for errors 106 | let fragment_shader = gl::CreateShader(gl::FRAGMENT_SHADER); 107 | gl::ShaderSource(fragment_shader, 1, &CString::new(fragment_source_cstring.as_bytes()).unwrap().as_ptr(), ptr::null()); 108 | gl::CompileShader(fragment_shader); 109 | 110 | let mut success = gl::FALSE as GLint; 111 | let mut info_log = Vec::with_capacity(512); 112 | info_log.set_len(512 - 1); // subtract 1 to skip trailing null char 113 | gl::GetShaderiv(fragment_shader, gl::COMPILE_STATUS, &mut success); 114 | if success != gl::TRUE as GLint { 115 | gl::GetShaderInfoLog(fragment_shader, 512, ptr::null_mut(), info_log.as_mut_ptr() as *mut GLchar); 116 | println!("FRAGMENT SHADER COMPILATION FAILED\n{:?}", stringify_vec_u8(info_log)); 117 | } 118 | 119 | (vertex_shader, fragment_shader) 120 | } 121 | 122 | pub unsafe fn use_program(&self) { 123 | gl::UseProgram(self.id); 124 | } 125 | 126 | // uniforms 127 | unsafe fn get_uniform_loc(&self, name: &str) -> i32 { 128 | gl::GetUniformLocation(self.id, CString::new(name).unwrap().as_ptr()) 129 | } 130 | 131 | pub unsafe fn set_bool(&self, name: &str, value: bool) { 132 | self.set_int(name, value as i32) 133 | } 134 | 135 | pub unsafe fn set_int(&self, name: &str, value: i32) { 136 | gl::Uniform1i(self.get_uniform_loc(name), value) 137 | } 138 | 139 | pub unsafe fn set_texture(&self, name: &str, value: &Texture) { 140 | self.set_uint(name, value.texture_id - gl::TEXTURE0) 141 | } 142 | 143 | pub unsafe fn set_uint(&self, name: &str, value: u32) { 144 | self.set_int(name, value as i32) 145 | } 146 | 147 | pub unsafe fn set_float(&self, name: &str, value: f32) { 148 | gl::Uniform1f(self.get_uniform_loc(name), value) 149 | } 150 | 151 | pub unsafe fn set_mat4(&self, name: &str, value: Matrix4) { 152 | gl::UniformMatrix4fv(self.get_uniform_loc(name), 1, gl::FALSE, value.as_ptr()); 153 | } 154 | 155 | pub unsafe fn set_vec3(&self, name: &str, value: Vector3) { 156 | gl::Uniform3f(self.get_uniform_loc(name), value.x, value.y, value.z); 157 | } 158 | } 159 | 160 | fn stringify_vec_u8(info_log: Vec) -> String { 161 | info_log.iter().map(|c| *c as u8 as char).collect::() 162 | } -------------------------------------------------------------------------------- /src/multiplayer/server_connection.rs: -------------------------------------------------------------------------------- 1 | use std::{io::{self, BufRead, BufReader, LineWriter, Write}, net::TcpStream, thread}; 2 | 3 | use cgmath::Vector3; 4 | use serde_json::Result; 5 | 6 | use crate::multiplayer::{rc_message::RustyCraftMessage, server_player::ServerPlayer}; 7 | 8 | use super::{event::RustyCraftEvent, server_state::ServerState}; 9 | 10 | // struct that abstracts reading and writing to a server 11 | pub struct ServerConnection { 12 | reader: BufReader, 13 | writer: LineWriter, 14 | stream: TcpStream, 15 | pub address: String 16 | } 17 | 18 | impl Clone for ServerConnection { 19 | fn clone(&self) -> Self { 20 | let writer = LineWriter::new(self.stream.try_clone().unwrap()); 21 | let reader = BufReader::new(self.stream.try_clone().unwrap()); 22 | ServerConnection { reader, writer, stream: self.stream.try_clone().unwrap(), address: self.address.clone() } 23 | } 24 | } 25 | 26 | impl ServerConnection { 27 | pub fn new(mut address: String) -> io::Result { 28 | // default port 29 | if !address.contains(":") { 30 | address.push_str(":25566"); 31 | } 32 | 33 | let stream = TcpStream::connect(address.clone()); 34 | if stream.is_err() { 35 | return Err(stream.unwrap_err()); 36 | } 37 | 38 | let stream = stream.unwrap(); 39 | let writer = LineWriter::new(stream.try_clone()?); 40 | let reader = BufReader::new(stream.try_clone()?); 41 | Ok(ServerConnection { reader, writer, address, stream }) 42 | } 43 | 44 | fn send(&mut self, message: &str) -> io::Result<()> { 45 | self.writer.write(&message.as_bytes())?; 46 | // send breakline to flush writer 47 | self.writer.write(&[b'\n'])?; 48 | Ok(()) 49 | } 50 | 51 | pub fn send_message(&mut self, event: RustyCraftMessage) -> io::Result<()> { 52 | self.send(serde_json::to_string(&event).unwrap().as_str()) 53 | } 54 | 55 | pub fn read(&mut self) -> io::Result> { 56 | let mut line = String::new(); 57 | let bytes_read = self.reader.read_line(&mut line)?; 58 | match bytes_read { 59 | 0 => Ok(None), 60 | _ => { 61 | line.pop(); 62 | Ok(Some(line)) 63 | } 64 | } 65 | } 66 | 67 | pub fn create_listen_thread(mut self, state: ServerState) { 68 | thread::spawn(move || { 69 | loop { 70 | let result = self.read().unwrap(); 71 | match result { 72 | None => break, 73 | Some(data) => { 74 | let event: Result = serde_json::from_str(data.as_str()); 75 | if event.is_err() { 76 | println!("Received invalid event"); 77 | continue; 78 | } 79 | 80 | match event.unwrap() { 81 | RustyCraftEvent { sender: _, message: RustyCraftMessage::ChunkData { chunks } } => { 82 | let mut world = state.world.lock().unwrap(); 83 | for (chunk_x, chunk_z, serialized_chunk) in chunks.into_iter() { 84 | world.insert_serialized_chunk(chunk_x, chunk_z, serialized_chunk); 85 | } 86 | }, 87 | RustyCraftEvent { sender: _, message: RustyCraftMessage::SetBlock { world_x, world_y, world_z, block } } => { 88 | let mut server_world = state.world.lock().unwrap(); 89 | server_world.set_block(world_x, world_y, world_z, block); 90 | server_world.recalculate_mesh_from_player_perspective(); 91 | }, 92 | // set name can only be done once after player joins, so use it to broadcast 93 | // join message 94 | RustyCraftEvent { sender, message: RustyCraftMessage::PlayerInit { name, x, y, z } } => { 95 | state.players.lock().unwrap().insert(sender.clone(), ServerPlayer::new(sender, name.clone(), x, y, z, 0.0, -90.0)); 96 | state.chat_stack.lock().unwrap().push(format!("{} joined the server", name)); 97 | }, 98 | RustyCraftEvent { sender, message: RustyCraftMessage::PlayerPosition { x, y, z } } => { 99 | if let Some(player) = state.players.lock().unwrap().get_mut(&sender) { 100 | player.position = Vector3::new(x, y, z); 101 | } 102 | }, 103 | RustyCraftEvent { sender, message: RustyCraftMessage::PlayerDirection { yaw, pitch } } => { 104 | if let Some(player) = state.players.lock().unwrap().get_mut(&sender) { 105 | player.yaw = yaw; 106 | player.pitch = pitch; 107 | } 108 | }, 109 | RustyCraftEvent { sender, message: RustyCraftMessage::ChatMessage { content } } => { 110 | let message = match state.players.lock().unwrap().get(&sender) { 111 | Some(player) => format!("<{}> {}", player.name, content), 112 | None => format!(" {}", content) 113 | }; 114 | state.chat_stack.lock().unwrap().push(message); 115 | }, 116 | RustyCraftEvent { sender: _, message: RustyCraftMessage::ConnectionData { id, players } } => { 117 | *state.client_id.lock().unwrap() = id; 118 | for (id, name, x, y, z, yaw, pitch) in players.iter() { 119 | state.players.lock().unwrap().insert(id.clone(), ServerPlayer::new(id.clone(), name.clone(), *x, *y, *z, *pitch, *yaw)); 120 | } 121 | }, 122 | RustyCraftEvent { sender, message: RustyCraftMessage::Disconnect } => { 123 | let mut players = state.players.lock().unwrap(); 124 | let player = players.get(&sender); 125 | // handle if peer never sent SetName packet 126 | let message = match player { 127 | Some(player) => format!("{} left the server", player.name), 128 | None => String::from("[Unnamed Player] left the server") 129 | }; 130 | 131 | if player.is_some() { 132 | players.remove(&sender); 133 | } 134 | state.chat_stack.lock().unwrap().push(message); 135 | } 136 | event => { 137 | println!("Received unhandled event: {:?}", event); 138 | } 139 | } 140 | } 141 | } 142 | } 143 | }); 144 | } 145 | } -------------------------------------------------------------------------------- /src/core/world.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, rc::Rc}; 2 | use std::time::{SystemTime, UNIX_EPOCH}; 3 | 4 | use cgmath::{Vector3, InnerSpace}; 5 | use noise::{OpenSimplex, Seedable}; 6 | 7 | use crate::{traits::{game_chunk::GameChunk, game_world::GameWorld}, utils::{num_utils::distance, world_utils::localize_coords_to_chunk}}; 8 | 9 | use super::{block_type::BlockType, chunk::{CHUNK_HEIGHT, Chunk}, coord_map::CoordMap, face::Face}; 10 | 11 | // Vector of Rc of a tuple of opaque and then transparent block point vertices 12 | type WorldMesh = Vec, Vec)>>; 13 | 14 | #[derive(Clone)] 15 | pub struct World { 16 | chunks: CoordMap, 17 | render_distance: u32, 18 | simplex: OpenSimplex, 19 | player_chunk_x: i32, 20 | player_chunk_z: i32, 21 | pub save_dir: String, 22 | mesh: WorldMesh 23 | } 24 | 25 | impl GameWorld for World { 26 | fn get_block(&self, world_x: i32, world_y: i32, world_z: i32) -> Option { 27 | let (chunk_x, chunk_z, local_x, local_z) = localize_coords_to_chunk(world_x, world_z); 28 | let chunk = self.get_chunk(chunk_x, chunk_z); 29 | if chunk.is_none() || world_y < 0 || world_y >= CHUNK_HEIGHT as i32 { 30 | return None 31 | } 32 | 33 | let result = Some(chunk.unwrap().block_at(local_x, world_y as usize, local_z)); 34 | result 35 | } 36 | 37 | fn get_game_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&dyn GameChunk> { 38 | let result = self.chunks.get(chunk_x, chunk_z); 39 | match result { 40 | Some(chunk) => { 41 | Some(chunk as &dyn GameChunk) 42 | }, 43 | None => None 44 | } 45 | } 46 | } 47 | 48 | // handles world block data and rendering 49 | impl World { 50 | pub fn new_with_seed(render_distance: u32, save_dir: &str, seed: u32) -> World { 51 | // create world directory if it does not exist 52 | let dir = format!("game_data/worlds/{}/chunks", save_dir); 53 | fs::create_dir_all(dir.clone()) 54 | .expect(format!("Failed to recursively create {}", dir.clone()).as_str()); 55 | 56 | let chunks = CoordMap::new(); 57 | let simplex = OpenSimplex::new().set_seed(seed); 58 | 59 | let save_dir = format!("game_data/worlds/{}", save_dir); 60 | World { chunks, render_distance, simplex, player_chunk_x: 0, player_chunk_z: 0, save_dir, mesh: vec![] } 61 | } 62 | 63 | pub fn new(render_distance: u32, save_dir: &str) -> World { 64 | let seed_path = format!("game_data/worlds/{}/seed", save_dir); 65 | let seed = fs::read_to_string(seed_path.clone()); 66 | // read seed from world dir otherwise create 67 | // one and write to disk 68 | let seed = match seed { 69 | Ok(seed) => seed.parse::().unwrap(), 70 | Err(_) => { 71 | let seed = SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_millis() as u32; 72 | fs::create_dir_all(format!("game_data/worlds/{}", save_dir)) 73 | .expect("Failed to create world directory"); 74 | fs::write(seed_path.clone(), format!("{}", seed)) 75 | .expect(format!("Failed to write seed to {}", seed_path).as_str()); 76 | seed 77 | } 78 | }; 79 | World::new_with_seed(render_distance, save_dir, seed) 80 | } 81 | 82 | pub fn get_world_mesh_from_perspective(&mut self, player_x: i32, player_z: i32, force: bool) -> &WorldMesh { 83 | let player_chunk_x = player_x / 16; 84 | let player_chunk_z = player_z / 16; 85 | if !force 86 | && self.mesh.len() > 0 87 | && self.player_chunk_x == player_chunk_x 88 | && self.player_chunk_z == player_chunk_z { 89 | return &self.mesh 90 | } 91 | 92 | self.recalculate_mesh_from_perspective(player_chunk_x, player_chunk_z); 93 | 94 | self.player_chunk_x = player_chunk_x; 95 | self.player_chunk_z = player_chunk_z; 96 | 97 | &self.mesh 98 | } 99 | 100 | pub fn recalculate_mesh_from_perspective(&mut self, player_chunk_x: i32, player_chunk_z: i32) { 101 | let mut meshes = Vec::new(); 102 | let mut chunks_in_view = Vec::new(); 103 | for x in 0..self.render_distance * 2 { 104 | let x = (x as i32) - (self.render_distance as i32) + player_chunk_x; 105 | for z in 0..self.render_distance * 2 { 106 | let z = (z as i32) - (self.render_distance as i32) + player_chunk_z; 107 | if distance(player_chunk_x, player_chunk_z, x, z) > self.render_distance as f32 { 108 | continue; 109 | } 110 | 111 | self.get_or_insert_chunk(x, z); 112 | self.get_or_insert_chunk(x + 1, z); 113 | self.get_or_insert_chunk(x - 1, z); 114 | self.get_or_insert_chunk(x, z + 1); 115 | self.get_or_insert_chunk(x, z - 1); 116 | chunks_in_view.push((x, z)); 117 | } 118 | } 119 | 120 | for (x, z) in chunks_in_view.iter() { 121 | let x = *x; 122 | let z = *z; 123 | 124 | let mesh; 125 | let chunk = self.get_chunk(x, z).unwrap(); 126 | if chunk.mesh.0.len() != 0 { 127 | mesh = chunk.mesh.clone(); 128 | } else { 129 | let right_chunk = self.get_chunk(x + 1, z).unwrap(); 130 | let left_chunk = self.get_chunk(x - 1, z).unwrap(); 131 | let front_chunk = self.get_chunk(x, z + 1).unwrap(); 132 | let back_chunk = self.get_chunk(x, z - 1).unwrap(); 133 | mesh = chunk.gen_mesh(right_chunk, left_chunk, front_chunk, back_chunk); 134 | } 135 | 136 | let chunk = self.get_chunk_mut(x, z).unwrap(); 137 | chunk.mesh = mesh; 138 | meshes.push(chunk.mesh.clone()); 139 | } 140 | 141 | self.mesh = meshes; 142 | } 143 | 144 | pub fn get_or_insert_chunk(&mut self, chunk_x: i32, chunk_z: i32) -> &Chunk { 145 | match self.chunks.contains(chunk_x, chunk_z) { 146 | true => self.chunks.get(chunk_x, chunk_z).unwrap(), 147 | false => { 148 | let c = Chunk::new(chunk_x, chunk_z, self.simplex.clone(), format!("{}/chunks", self.save_dir)); 149 | self.chunks.insert(chunk_x, chunk_z, c); 150 | self.chunks.get(chunk_x, chunk_z).unwrap() 151 | } 152 | } 153 | } 154 | 155 | pub fn get_chunk_mut(&mut self, chunk_x: i32, chunk_z: i32) -> Option<&mut Chunk> { 156 | match self.chunks.contains(chunk_x, chunk_z) { 157 | true => self.chunks.get_mut(chunk_x, chunk_z), 158 | false => None 159 | } 160 | } 161 | 162 | pub fn get_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&Chunk> { 163 | self.chunks.get(chunk_x, chunk_z) 164 | } 165 | 166 | pub fn set_block(&mut self, world_x: i32, world_y: i32, world_z: i32, block: BlockType) { 167 | let (chunk_x, chunk_z, local_x, local_z) = localize_coords_to_chunk(world_x, world_z); 168 | 169 | // set block 170 | { 171 | let chunk = self.get_chunk_mut(chunk_x, chunk_z).unwrap(); 172 | chunk.set_block(local_x, world_y as usize, local_z, block); 173 | } 174 | 175 | // update chunk mesh 176 | self.update_chunk_mesh(chunk_x, chunk_z); 177 | if local_x == 0 { 178 | self.update_chunk_mesh(chunk_x - 1, chunk_z) 179 | } else if local_x == 15 { 180 | self.update_chunk_mesh(chunk_x + 1, chunk_z) 181 | } else if local_z == 0 { 182 | self.update_chunk_mesh(chunk_x, chunk_z - 1) 183 | } else if local_z == 15 { 184 | self.update_chunk_mesh(chunk_x, chunk_z + 1) 185 | } 186 | } 187 | 188 | fn update_chunk_mesh(&mut self, chunk_x: i32, chunk_z: i32) { 189 | // assume adjacent chunks exist 190 | let right_chunk = self.get_chunk(chunk_x + 1, chunk_z).unwrap(); 191 | let left_chunk = self.get_chunk(chunk_x - 1, chunk_z).unwrap(); 192 | let front_chunk = self.get_chunk(chunk_x, chunk_z + 1).unwrap(); 193 | let back_chunk = self.get_chunk(chunk_x, chunk_z - 1).unwrap(); 194 | let chunk = self.get_chunk(chunk_x, chunk_z).unwrap(); 195 | let mesh = chunk.gen_mesh(right_chunk, left_chunk, front_chunk, back_chunk); 196 | 197 | self.get_chunk_mut(chunk_x, chunk_z).unwrap().mesh = mesh; 198 | } 199 | 200 | pub fn raymarch_block(&mut self, position: &Vector3, direction: &Vector3) -> Option<((i32, i32, i32), Option)> { 201 | let mut check_position = *position; 202 | let dir: Vector3 = *direction / 10.0; 203 | let mut range = 250; 204 | 205 | let mut result = Vec::new(); 206 | loop { 207 | check_position = check_position + dir; 208 | let x = check_position.x.round() as i32; 209 | let y = check_position.y.round() as i32; 210 | let z = check_position.z.round() as i32; 211 | result.push((x, y, z)); 212 | 213 | let block = self.get_block(x, y, z); 214 | if let Some(block) = block { 215 | if block != BlockType::Air && block != BlockType::Water { 216 | let vector = (*position - (check_position - dir)).normalize(); 217 | let abs_x = vector.x.abs(); 218 | let abs_y = vector.y.abs(); 219 | let abs_z = vector.z.abs(); 220 | let mut face = None; 221 | 222 | let mut face_is_x = false; 223 | // get cube face from ray direction 224 | // negated ray is on x-axis 225 | let sign = signum(vector.x); 226 | if self.moveable(x + sign, y, z) { 227 | face = if vector.x > 0.0 { 228 | Some(Face::Right) 229 | } else { 230 | Some(Face::Left) 231 | }; 232 | face_is_x = true; 233 | } 234 | 235 | if face.is_none() || abs_y > abs_x { 236 | // negated ray is on y-axis 237 | let sign = signum(vector.y); 238 | if self.moveable(x, y + sign, z) { 239 | face = if vector.y > 0.0 { 240 | Some(Face::Top) 241 | } else { 242 | Some(Face::Bottom) 243 | }; 244 | face_is_x = false; 245 | } 246 | } 247 | 248 | let sign = signum(vector.z); 249 | if face.is_none() || if face_is_x { abs_z > abs_x } else { abs_z > abs_y } { 250 | // negated ray is on z-axis 251 | //let sign = signum(vector.z); 252 | if self.moveable(x, y, z + sign) { 253 | face = if vector.z > 0.0 { 254 | Some(Face::Back) 255 | } else { 256 | Some(Face::Front) 257 | } 258 | } 259 | } 260 | 261 | return Some(((x, y, z), face)); 262 | } 263 | } 264 | 265 | if range == 0 { 266 | return None; 267 | } 268 | range = range - 1; 269 | } 270 | } 271 | } 272 | 273 | fn signum(n: f32) -> i32 { 274 | if n > 0.0 { 275 | 1 276 | } else { 277 | -1 278 | } 279 | } -------------------------------------------------------------------------------- /src/core/chunk.rs: -------------------------------------------------------------------------------- 1 | use std::{fs, rc::Rc}; 2 | 3 | use noise::OpenSimplex; 4 | 5 | use rand::prelude::*; 6 | 7 | use crate::{traits::game_chunk::GameChunk, utils::{chunk_utils::{from_serialized, to_serialized}, simplex_utils::sample}}; 8 | 9 | use super::{block_map::BlockMap, block_type::{BlockType, block_to_uv}, face::Face}; 10 | 11 | pub const CHUNK_SIZE: usize = 16; 12 | pub const CHUNK_HEIGHT: usize = 256; 13 | 14 | #[derive(Clone)] 15 | pub struct Chunk { 16 | pub blocks: BlockMap, 17 | pub blocks_in_mesh: Vec<(usize, usize, usize)>, 18 | x: i32, 19 | z: i32, 20 | save_path: String, 21 | pub mesh: Rc<(Vec, Vec)> // cache mesh 22 | } 23 | 24 | impl GameChunk for Chunk { 25 | fn get_blocks(&self) -> &BlockMap { 26 | &self.blocks 27 | } 28 | } 29 | 30 | impl Chunk { 31 | pub fn from(save_path: String, contents: String, x: i32, z: i32) -> Chunk { 32 | let (blocks_in_mesh, blocks) = from_serialized(&contents); 33 | Chunk { blocks, blocks_in_mesh, x: x * 16, z: z * 16, save_path, mesh: Rc::new((vec![], vec![])) } 34 | } 35 | 36 | pub fn new(x_offset: i32, z_offset: i32, simplex: OpenSimplex, chunk_dir: String) -> Chunk { 37 | let save_path = format!("{}/{}_{}", chunk_dir, x_offset, z_offset); 38 | let contents = fs::read_to_string(save_path.clone()); 39 | if let Ok(contents) = contents { 40 | return Chunk::from(save_path, contents, x_offset, z_offset) 41 | } 42 | 43 | let amplitude = 15.0; 44 | let mut blocks = BlockMap::new(); 45 | let mut blocks_in_mesh = Vec::new(); 46 | let x_offset = x_offset * 16; 47 | let z_offset = z_offset * 16; 48 | for x in 0..CHUNK_SIZE { 49 | for z in 0..CHUNK_SIZE { 50 | let simplex_x = (x as i32 + x_offset) as f32; 51 | let simplex_z = (z as i32 + z_offset) as f32; 52 | let noise = gen_heightmap(simplex_x, simplex_z, simplex); 53 | let height = ((amplitude * noise) as usize) + 1; 54 | if height < 10 { 55 | for y in 0..9 { 56 | let block = if y < height - 1 { 57 | BlockType::Sand 58 | } else { 59 | BlockType::Water 60 | }; 61 | add_block(&mut blocks, &mut blocks_in_mesh, x, y, z, block); 62 | } 63 | } else { 64 | let snow_offset = (sample(simplex_x * 4.0, simplex_z * 4.0, simplex) * 20.0) as usize; 65 | for y in 0..height { 66 | let block = if y == height - 1 { 67 | if height > 30 + snow_offset { 68 | BlockType::Snow 69 | } else if height > 30 - snow_offset { 70 | BlockType::Stone 71 | } else if height == 10 { 72 | BlockType::Sand 73 | } else { 74 | BlockType::Grass 75 | } 76 | } else if y > height - 3 { 77 | if height > 20 { 78 | BlockType::Stone 79 | } else { 80 | BlockType::Dirt 81 | } 82 | } else { 83 | BlockType::Stone 84 | }; 85 | add_block(&mut blocks, &mut blocks_in_mesh, x, y, z, block); 86 | } 87 | } 88 | } 89 | } 90 | 91 | // tree generation logic (hacked together, refactor later) 92 | let mut rng = rand::thread_rng(); 93 | if rng.gen::() < 0.9 { 94 | let x = (rng.gen::() * 11.0) as usize + 3; 95 | let z = (rng.gen::() * 11.0) as usize + 3; 96 | let top = blocks.highest_in_column(x, z); 97 | let block = blocks.get(x, top, z); 98 | if block != BlockType::Water && block != BlockType::Stone && block != BlockType::Sand && block != BlockType::Snow { 99 | // trunk 100 | for i in 1..4 { 101 | add_block(&mut blocks, &mut blocks_in_mesh, x, top + i, z, BlockType::Log); 102 | } 103 | 104 | // leaf layer 105 | for ix in 0..3 { 106 | for iz in 0..3 { 107 | add_block(&mut blocks, &mut blocks_in_mesh, x + 1 - ix, top + 3, z + 1 - iz, BlockType::Leaves); 108 | } 109 | } 110 | 111 | // second layer 112 | add_block(&mut blocks, &mut blocks_in_mesh, x, top + 4, z, BlockType::Leaves); 113 | add_block(&mut blocks, &mut blocks_in_mesh, x + 1, top + 4, z, BlockType::Leaves); 114 | add_block(&mut blocks, &mut blocks_in_mesh, x - 1, top + 4, z, BlockType::Leaves); 115 | add_block(&mut blocks, &mut blocks_in_mesh, x, top + 4, z + 1, BlockType::Leaves); 116 | add_block(&mut blocks, &mut blocks_in_mesh, x, top + 4, z - 1, BlockType::Leaves); 117 | 118 | // highest leaf block 119 | add_block(&mut blocks, &mut blocks_in_mesh, x, top + 5, z, BlockType::Leaves); 120 | } 121 | } 122 | 123 | let chunk = Chunk { blocks, blocks_in_mesh, x: x_offset, z: z_offset, save_path, mesh: Rc::new((vec![], vec![])) }; 124 | chunk.save(); 125 | chunk 126 | } 127 | 128 | pub fn gen_mesh(&self, right_chunk: &Chunk, left_chunk: &Chunk, front_chunk: &Chunk, back_chunk: &Chunk) -> Rc<(Vec, Vec)> { 129 | let mut vertices = Vec::new(); 130 | // water is transparent so is in separate 131 | // vector to draw after opaque blocks 132 | let mut water_vertices = Vec::new(); 133 | for (x, y, z) in self.blocks_in_mesh.iter() { 134 | // let instant = std::time::Instant::now(); 135 | let x = *x; 136 | let y = *y; 137 | let z = *z; 138 | 139 | let block = self.blocks.get(x, y, z); 140 | if block == BlockType::Air { 141 | continue; 142 | } 143 | 144 | let x = x as i32; 145 | let y = y as i32; 146 | let z = z as i32; 147 | let faces = 148 | 0 149 | | if self.can_place_mesh_face_at_block(x, y, z - 1, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b10000000 } else { 0 } 150 | | if self.can_place_mesh_face_at_block(x + 1, y, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b01000000 } else { 0 } 151 | | if self.can_place_mesh_face_at_block(x, y, z + 1, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00100000 } else { 0 } 152 | | if self.can_place_mesh_face_at_block(x, y - 1, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00010000 } else { 0 } 153 | | if self.can_place_mesh_face_at_block(x - 1, y, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00001000 } else { 0 } 154 | | if self.can_place_mesh_face_at_block(x, y + 1, z, block, right_chunk, left_chunk, front_chunk, back_chunk) { 0b00000100 } else { 0 }; 155 | 156 | 157 | if faces == 0 { 158 | continue; 159 | } 160 | 161 | let world_x = (x + self.x) as f32; 162 | let world_y = y as f32; 163 | let world_z = (z + self.z) as f32; 164 | 165 | let vertices_to_push_to = if block == BlockType::Water { 166 | &mut water_vertices 167 | }else { 168 | &mut vertices 169 | }; 170 | vertices_to_push_to.push(world_x); 171 | vertices_to_push_to.push(world_y); 172 | vertices_to_push_to.push(world_z); 173 | 174 | for i in 0..6 { 175 | let face = match i { 176 | 0 => Face::Front, 177 | 1 => Face::Right, 178 | 2 => Face::Back, 179 | 3 => Face::Bottom, 180 | 4 => Face::Left, 181 | 5 => Face::Top, 182 | _ => panic!("Attempted to convert invalid index to face when setting vertex texture UV indices") 183 | }; 184 | vertices_to_push_to.push(block_to_uv(block, face)); 185 | } 186 | 187 | vertices_to_push_to.push(faces as f32); 188 | } 189 | 190 | Rc::new((vertices, water_vertices)) 191 | } 192 | 193 | fn save(&self) { 194 | fs::write(self.save_path.clone(), to_serialized(&self.blocks_in_mesh, &self.blocks)) 195 | .expect(format!("Failed to save chunk to {}", self.save_path.clone()).as_str()); 196 | } 197 | 198 | pub fn block_at(&self, x: usize, y: usize, z: usize) -> BlockType { 199 | self.blocks.get(x, y, z) 200 | } 201 | 202 | pub fn set_block(&mut self, x: usize, y: usize, z: usize, block: BlockType) { 203 | self.blocks.set(x, y, z, block); 204 | if block == BlockType::Air { 205 | for i in 0..self.blocks_in_mesh.len() - 1 { 206 | if self.blocks_in_mesh[i] == (x, y, z) { 207 | self.blocks_in_mesh.remove(i); 208 | break; 209 | } 210 | } 211 | } else { 212 | self.blocks_in_mesh.push((x, y, z)); 213 | } 214 | self.save(); 215 | //self.gen_mesh(); 216 | } 217 | 218 | pub fn can_place_at_local_spot(&self, x: i32, y: i32, z: i32, block: BlockType) -> bool { 219 | if y < 0 { 220 | return false 221 | } 222 | 223 | // if x < 0 || x >= CHUNK_SIZE as i32 224 | // || y >= CHUNK_HEIGHT as i32 225 | // || z < 0 || z >= CHUNK_SIZE as i32 { 226 | // return true 227 | // } 228 | 229 | let block_spot = self.blocks.get(x as usize, y as usize, z as usize); 230 | block_spot == BlockType::Air || (block != BlockType::Water && block_spot == BlockType::Water) 231 | } 232 | 233 | pub fn can_place_mesh_face_at_block(&self, x: i32, y: i32, z: i32, block: BlockType, right_chunk: &Chunk, left_chunk: &Chunk, front_chunk: &Chunk, back_chunk: &Chunk) -> bool { 234 | if y < 0 { 235 | return false 236 | } 237 | 238 | // if outside own chunk fetch edge 239 | // of respective adjacent chunk 240 | if x == 16 { 241 | return right_chunk.can_place_at_local_spot(0, y, z, block); 242 | } else if x == -1 { 243 | return left_chunk.can_place_at_local_spot(15, y, z, block); 244 | } else if z == 16 { 245 | return front_chunk.can_place_at_local_spot(x, y, 0, block); 246 | } else if z == -1 { 247 | return back_chunk.can_place_at_local_spot(x, y, 15, block); 248 | } 249 | 250 | let block_spot = self.blocks.get(x as usize, y as usize, z as usize); 251 | block_spot == BlockType::Air || (block_spot == BlockType::Water && block != BlockType::Water) 252 | } 253 | } 254 | 255 | fn add_block(blocks: &mut BlockMap, blocks_in_mesh: &mut Vec<(usize, usize, usize)>, x: usize, y: usize, z: usize, block: BlockType) { 256 | blocks.set(x, y, z, block); 257 | blocks_in_mesh.push((x, y, z)); 258 | } 259 | 260 | fn gen_heightmap(x: f32, z: f32, simplex: OpenSimplex) -> f32 { 261 | let x = x / 100.0; 262 | let z = z / 100.0; 263 | let coeff = sample(x, z, simplex) * 2.0; 264 | let height = coeff * sample(x, z, simplex) 265 | + coeff * sample(2.0 * x, 2.0 * z, simplex) 266 | + 0.5 * coeff * sample(4.0 * x, 4.0 * z, simplex); 267 | height.powf(1.5) 268 | } -------------------------------------------------------------------------------- /src/opengl/text_renderer.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | use std::{collections::HashMap, ffi::c_void, ptr}; 3 | use cgmath::{Matrix4, SquareMatrix, Vector2, Vector3, ortho, vec2}; 4 | use freetype::{Library, ffi::FT_Pos}; 5 | use gl::types::*; 6 | 7 | use crate::opengl::camera::Camera; 8 | 9 | use super::shader::Shader; 10 | 11 | #[derive(PartialEq, Clone, Copy)] 12 | pub enum TextJustification { 13 | Left, 14 | Center 15 | } 16 | 17 | pub struct Character { 18 | texture_id: u32, // glyph texture ID 19 | size: Vector2, // size of glyph 20 | pub bearing: Vector2, // offset from baseline to left/top of glyph 21 | advance: FT_Pos // offset to advance to next glyph 22 | } 23 | 24 | pub struct TextRenderer { 25 | char_cache: HashMap, // cached glyph data calculated at construction 26 | vao: GLuint, // text rendering vertex attributes 27 | vbo: GLuint, // text rendering buffer 28 | vao3d: GLuint, // text rendering vertex attributes for 3d rendering 29 | vbo3d: GLuint, // text rendering buffer for 3d rendering 30 | shader: Shader, // shader for text rendering 31 | shader3d: Shader, // shader for text rendering 32 | screen_width: u32, // screen width 33 | screen_height: u32 // and screen height for calculating orthographic matrix 34 | } 35 | 36 | impl TextRenderer { 37 | pub unsafe fn new(screen_width: u32, screen_height: u32, font_face: &str) -> TextRenderer { 38 | // init freetype library 39 | let lib = Library::init().unwrap(); 40 | 41 | // load a font face 42 | let font = lib.new_face(font_face, 0).unwrap(); 43 | 44 | // font size 45 | font.set_pixel_sizes(0, 20).unwrap(); 46 | 47 | // disable byte-alignment restriction 48 | gl::PixelStorei(gl::UNPACK_ALIGNMENT, 1); 49 | 50 | // load shader 51 | let shader = Shader::new("assets/shaders/text/text_vertex.vert", "assets/shaders/text/text_fragment.frag"); 52 | let shader3d = Shader::new("assets/shaders/text/text_vertex3d.vert", "assets/shaders/text/text_fragment.frag"); 53 | 54 | // generate vertex array object 55 | let mut vao = 0; 56 | gl::GenVertexArrays(1, &mut vao); 57 | gl::BindVertexArray(vao); 58 | 59 | // allocate empty vertex buffer object for text rendering 60 | let mut vbo = 0; 61 | gl::GenBuffers(1, &mut vbo); 62 | gl::BindBuffer(gl::ARRAY_BUFFER, vbo); 63 | gl::BufferData( 64 | gl::ARRAY_BUFFER, 65 | 6 * 4 * std::mem::size_of::() as isize, 66 | ptr::null(), 67 | gl::DYNAMIC_DRAW 68 | ); 69 | gl::EnableVertexAttribArray(0); 70 | gl::VertexAttribPointer( 71 | 0, 72 | 4, 73 | gl::FLOAT, 74 | gl::FALSE, 75 | 4 * std::mem::size_of::() as i32, 76 | ptr::null() 77 | ); 78 | 79 | // generate vertex array object 80 | let mut vao3d = 0; 81 | gl::GenVertexArrays(1, &mut vao3d); 82 | gl::BindVertexArray(vao3d); 83 | 84 | // allocate empty vertex buffer object for text rendering 85 | let mut vbo3d = 0; 86 | gl::GenBuffers(1, &mut vbo3d); 87 | gl::BindBuffer(gl::ARRAY_BUFFER, vbo3d); 88 | gl::BufferData( 89 | gl::ARRAY_BUFFER, 90 | 6 * 5 * std::mem::size_of::() as isize, 91 | ptr::null(), 92 | gl::DYNAMIC_DRAW 93 | ); 94 | gl::EnableVertexAttribArray(0); 95 | gl::VertexAttribPointer( 96 | 0, 97 | 3, 98 | gl::FLOAT, 99 | gl::FALSE, 100 | 5 * std::mem::size_of::() as i32, 101 | ptr::null() 102 | ); 103 | gl::EnableVertexAttribArray(1); 104 | gl::VertexAttribPointer( 105 | 1, 106 | 2, 107 | gl::FLOAT, 108 | gl::FALSE, 109 | 5 * std::mem::size_of::() as i32, 110 | (3 * std::mem::size_of::()) as *const c_void 111 | ); 112 | 113 | // best practice to reset to defaults 114 | gl::BindBuffer(gl::ARRAY_BUFFER, 0); 115 | gl::BindVertexArray(0); 116 | 117 | let mut char_cache: HashMap = HashMap::new(); 118 | for c in 0..128 { 119 | // load character 120 | font.load_char(c, freetype::face::LoadFlag::RENDER) 121 | .expect(format!("Freetype: failed to load glyph with code '{}'", c).as_str()); 122 | 123 | let glyph = font.glyph(); 124 | let bitmap = glyph.bitmap(); 125 | 126 | // generate texture object and bind to state to edit 127 | let mut texture = 0; 128 | gl::GenTextures(1, &mut texture); 129 | gl::BindTexture(gl::TEXTURE_2D, texture); 130 | 131 | // add glyph texture data 132 | gl::TexImage2D( 133 | gl::TEXTURE_2D, 134 | 0, 135 | gl::RED as i32, 136 | bitmap.width(), 137 | bitmap.rows(), 138 | 0, 139 | gl::RED, 140 | gl::UNSIGNED_BYTE, 141 | if bitmap.buffer().len() == 0 { 142 | ptr::null() 143 | } else { 144 | bitmap.buffer().as_ptr() as *const c_void 145 | } 146 | ); 147 | 148 | // glyph texture parameters 149 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_S, gl::CLAMP_TO_EDGE as i32); 150 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_WRAP_T, gl::CLAMP_TO_EDGE as i32); 151 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MIN_FILTER, gl::LINEAR as i32); 152 | gl::TexParameteri(gl::TEXTURE_2D, gl::TEXTURE_MAG_FILTER, gl::LINEAR as i32); 153 | 154 | // add character object 155 | let character = Character { 156 | texture_id: texture, 157 | size: vec2(bitmap.width(), bitmap.rows()), 158 | bearing: vec2(glyph.bitmap_left(), glyph.bitmap_top()), 159 | advance: glyph.advance().x 160 | }; 161 | char_cache.insert(c, character); 162 | } 163 | TextRenderer { char_cache, vao, vbo, vao3d, vbo3d, shader, shader3d, screen_width, screen_height } 164 | } 165 | 166 | pub unsafe fn render_text(&self, text: &str, x: f32, y: f32, scale: f32, color: Vector3, justification: TextJustification) { 167 | self.render_text_with_mat(text, x, y, scale, color, SquareMatrix::identity(), justification); 168 | } 169 | 170 | pub fn get_char(&self, c: char) -> &Character { 171 | &self.char_cache[&(c as usize)] 172 | } 173 | 174 | pub fn calc_width(&self, string: &str, scale: f32) -> f32 { 175 | let mut w = 0.0; 176 | for c in string.bytes() { 177 | let ch = &self.char_cache[&(c as usize)]; 178 | w += ((ch.advance >> 6) as f32) * scale; 179 | } 180 | w 181 | } 182 | 183 | pub unsafe fn render_text3d(&self, camera: &Camera, text: &str, mut x: f32, y: f32, z: f32, scale: f32, color: Vector3, justification: TextJustification) { 184 | self.shader3d.use_program(); 185 | self.shader3d.set_mat4("view", camera.get_view()); 186 | self.shader3d.set_mat4("projection", camera.get_projection()); 187 | self.shader3d.set_vec3("textColor", color); 188 | 189 | self.shader3d.set_mat4("model", SquareMatrix::identity()); 190 | gl::ActiveTexture(gl::TEXTURE0); 191 | gl::BindVertexArray(self.vao3d); 192 | 193 | if justification == TextJustification::Center { 194 | let w = self.calc_width(text, scale); 195 | x = x - w / 2.0; 196 | } 197 | 198 | for c in text.bytes() { 199 | let ch = &self.char_cache[&(c as usize)]; 200 | 201 | let x_pos = x + (ch.bearing.x as f32) * scale; 202 | let y_pos = match c { 203 | // hacky solution to properly adjust '-' and '^' respectively 204 | 39 => y + (ch.bearing.y as f32 / 2.0), 205 | 45 => y + (ch.bearing.y as f32 / 2.0), 206 | 94 => y + ch.bearing.y as f32 / 4.0, 207 | _ => y 208 | }; 209 | 210 | let w = (ch.size.x as f32) * scale; 211 | let h = (ch.size.y as f32) * scale; 212 | 213 | // generate vertices for charatcer 214 | let vertices: [[f32; 5]; 6] = [ 215 | [ x_pos, y_pos + h, z, 0.0, 0.0 ], 216 | [ x_pos, y_pos, z, 0.0, 1.0 ], 217 | [ x_pos + w, y_pos, z, 1.0, 1.0 ], 218 | 219 | [ x_pos, y_pos + h, z, 0.0, 0.0 ], 220 | [ x_pos + w, y_pos, z, 1.0, 1.0 ], 221 | [ x_pos + w, y_pos + h, z, 1.0, 0.0 ] 222 | ]; 223 | 224 | // render glyph texture over quad 225 | gl::BindTexture(gl::TEXTURE_2D, ch.texture_id); 226 | 227 | // update VBO memory 228 | gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo3d); 229 | gl::BufferSubData(gl::ARRAY_BUFFER, 0, std::mem::size_of_val(&vertices) as isize, vertices.as_ptr() as *const c_void); 230 | gl::BindBuffer(gl::ARRAY_BUFFER, 0); 231 | 232 | // render quad 233 | gl::DrawArrays(gl::TRIANGLES, 0, 6); 234 | 235 | x += ((ch.advance >> 6) as f32) * scale; 236 | } 237 | gl::BindVertexArray(0); 238 | gl::BindTexture(gl::TEXTURE_2D, 0); 239 | } 240 | 241 | pub unsafe fn render_text_with_mat(&self, text: &str, mut x: f32, y: f32, scale: f32, color: Vector3, model: Matrix4, justification: TextJustification) { 242 | self.shader.use_program(); 243 | self.shader.set_mat4("projection", ortho(0.0, self.screen_width as f32, 0.0, self.screen_height as f32, -1.0, 100.0)); 244 | self.shader.set_vec3("textColor", color); 245 | self.shader.set_mat4("model", model); 246 | gl::ActiveTexture(gl::TEXTURE0); 247 | gl::BindVertexArray(self.vao); 248 | 249 | if justification == TextJustification::Center { 250 | let w = self.calc_width(text, scale); 251 | x = x - w / 2.0; 252 | } 253 | 254 | for c in text.bytes() { 255 | let ch = &self.char_cache[&(c as usize)]; 256 | 257 | let x_pos = x + (ch.bearing.x as f32) * scale; 258 | let y_pos = match c { 259 | // hacky solution to properly adjust '-' and '^' respectively 260 | 39 => y + (ch.bearing.y as f32 / 2.0), 261 | 45 => y + (ch.bearing.y as f32 / 2.0), 262 | 94 => y + ch.bearing.y as f32 / 4.0, 263 | _ => y 264 | }; 265 | 266 | let w = (ch.size.x as f32) * scale; 267 | let h = (ch.size.y as f32) * scale; 268 | 269 | // generate vertices for charatcer 270 | let vertices: [[f32; 4]; 6] = [ 271 | [ x_pos, y_pos + h, 0.0, 0.0 ], 272 | [ x_pos, y_pos, 0.0, 1.0 ], 273 | [ x_pos + w, y_pos, 1.0, 1.0 ], 274 | 275 | [ x_pos, y_pos + h, 0.0, 0.0 ], 276 | [ x_pos + w, y_pos, 1.0, 1.0 ], 277 | [ x_pos + w, y_pos + h, 1.0, 0.0 ] 278 | ]; 279 | 280 | // render glyph texture over quad 281 | gl::BindTexture(gl::TEXTURE_2D, ch.texture_id); 282 | 283 | // update VBO memory 284 | gl::BindBuffer(gl::ARRAY_BUFFER, self.vbo); 285 | gl::BufferSubData(gl::ARRAY_BUFFER, 0, std::mem::size_of_val(&vertices) as isize, vertices.as_ptr() as *const c_void); 286 | gl::BindBuffer(gl::ARRAY_BUFFER, 0); 287 | 288 | // render quad 289 | gl::DrawArrays(gl::TRIANGLES, 0, 6); 290 | 291 | x += ((ch.advance >> 6) as f32) * scale; 292 | } 293 | gl::BindVertexArray(0); 294 | gl::BindTexture(gl::TEXTURE_2D, 0); 295 | } 296 | } -------------------------------------------------------------------------------- /src/multiplayer/server_world.rs: -------------------------------------------------------------------------------- 1 | // world struct for managing and 2 | // parsing world data from a server 3 | use std::{collections::HashSet, sync::Arc}; 4 | use cgmath::{Vector3, InnerSpace}; 5 | use crate::{core::{block_type::BlockType, coord_map::CoordMap, face::Face}, multiplayer::{rc_message::RustyCraftMessage, server_chunk::ServerChunk}, traits::{game_chunk::GameChunk, game_world::GameWorld}}; 6 | use super::server_connection::ServerConnection; 7 | 8 | // Vector of Arc (to be thread-safe with server connection listen thread) 9 | // of a tuple of opaque and then transparent block point vertices 10 | type WorldMesh = Vec, Vec)>>; 11 | 12 | #[derive(Clone)] 13 | pub struct ServerWorld { 14 | chunks: CoordMap, 15 | render_distance: u32, 16 | player_chunk_x: i32, 17 | player_chunk_z: i32, 18 | mesh: WorldMesh, 19 | chunk_fetch_queue: HashSet<(i32, i32)>, 20 | server_connection: ServerConnection 21 | } 22 | 23 | impl GameWorld for ServerWorld { 24 | fn get_block(&self, world_x: i32, world_y: i32, world_z: i32) -> Option { 25 | let (chunk_x, chunk_z, local_x, local_z) = self.localize_coords_to_chunk(world_x, world_z); 26 | let chunk = self.get_chunk(chunk_x, chunk_z); 27 | if chunk.is_none() || world_y < 0 { 28 | return None 29 | } 30 | 31 | let result = Some(chunk.unwrap().block_at(local_x, world_y as usize, local_z)); 32 | result 33 | } 34 | 35 | fn get_game_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&dyn GameChunk> { 36 | let result = self.chunks.get(chunk_x, chunk_z); 37 | match result { 38 | Some(chunk) => { 39 | Some(chunk as &dyn GameChunk) 40 | }, 41 | None => None 42 | } 43 | } 44 | } 45 | 46 | // handles world block data and rendering 47 | impl ServerWorld { 48 | pub fn new(render_distance: u32, server_connection: ServerConnection) -> ServerWorld { 49 | ServerWorld { 50 | chunks: CoordMap::new(), 51 | render_distance, 52 | player_chunk_x: 0, 53 | player_chunk_z: 0, 54 | mesh: WorldMesh::new(), 55 | chunk_fetch_queue: HashSet::new(), 56 | server_connection 57 | } 58 | } 59 | 60 | pub fn get_world_mesh_from_perspective(&mut self, player_x: i32, player_z: i32, force: bool) -> &WorldMesh { 61 | let player_chunk_x = player_x / 16; 62 | let player_chunk_z = player_z / 16; 63 | if !force 64 | && self.player_chunk_x == player_chunk_x 65 | && self.player_chunk_z == player_chunk_z { 66 | return &self.mesh 67 | } 68 | 69 | self.recalculate_mesh_from_perspective(player_chunk_x, player_chunk_z); 70 | 71 | self.player_chunk_x = player_chunk_x; 72 | self.player_chunk_z = player_chunk_z; 73 | 74 | &self.mesh 75 | } 76 | 77 | pub fn recalculate_mesh_from_player_perspective(&mut self) { 78 | self.recalculate_mesh_from_perspective(self.player_chunk_x, self.player_chunk_z); 79 | } 80 | 81 | pub fn recalculate_mesh_from_perspective(&mut self, player_chunk_x: i32, player_chunk_z: i32) { 82 | let mut meshes = Vec::new(); 83 | let mut chunks_in_view = Vec::new(); 84 | 85 | self.chunk_fetch_queue.clear(); 86 | for x in 0..self.render_distance * 2 { 87 | let x = (x as i32) - (self.render_distance as i32) + player_chunk_x; 88 | for z in 0..self.render_distance * 2 { 89 | let z = (z as i32) - (self.render_distance as i32) + player_chunk_z; 90 | if (((player_chunk_x - x).pow(2) + (player_chunk_z - z).pow(2)) as f32).sqrt() > self.render_distance as f32 { 91 | continue; 92 | } 93 | 94 | let contains_chunk = self.contains_chunk(x, z); 95 | let contains_chunk_right = self.contains_chunk(x + 1, z); 96 | let contains_chunk_left = self.contains_chunk(x - 1, z); 97 | let contains_chunk_front = self.contains_chunk(x, z + 1); 98 | let contains_chunk_back = self.contains_chunk(x, z - 1); 99 | if contains_chunk 100 | && contains_chunk_right 101 | && contains_chunk_left 102 | && contains_chunk_front 103 | && contains_chunk_back { 104 | chunks_in_view.push((x, z)); 105 | } else { 106 | if !contains_chunk { 107 | self.chunk_fetch_queue.insert((x, z)); 108 | } 109 | 110 | if !contains_chunk_right { 111 | self.chunk_fetch_queue.insert((x + 1, z)); 112 | } 113 | 114 | if !contains_chunk_left { 115 | self.chunk_fetch_queue.insert((x - 1, z)); 116 | } 117 | 118 | if !contains_chunk_front { 119 | self.chunk_fetch_queue.insert((x, z + 1)); 120 | } 121 | 122 | if !contains_chunk_back { 123 | self.chunk_fetch_queue.insert((x, z - 1)); 124 | } 125 | } 126 | } 127 | } 128 | 129 | for (x, z) in chunks_in_view.iter() { 130 | let x = *x; 131 | let z = *z; 132 | 133 | let mesh; 134 | let chunk = self.get_chunk(x, z).unwrap(); 135 | if chunk.mesh.0.len() != 0 { 136 | mesh = chunk.mesh.clone(); 137 | } else { 138 | let right_chunk = self.get_chunk(x + 1, z).unwrap(); 139 | let left_chunk = self.get_chunk(x - 1, z).unwrap(); 140 | let front_chunk = self.get_chunk(x, z + 1).unwrap(); 141 | let back_chunk = self.get_chunk(x, z - 1).unwrap(); 142 | mesh = chunk.gen_mesh(right_chunk, left_chunk, front_chunk, back_chunk); 143 | } 144 | 145 | let chunk = self.get_chunk_mut(x, z).unwrap(); 146 | chunk.mesh = mesh; 147 | meshes.push(chunk.mesh.clone()); 148 | } 149 | 150 | // fetch chunks 151 | if self.chunk_fetch_queue.len() > 0 { 152 | self.server_connection.send_message(RustyCraftMessage::GetChunks { coords: self.chunk_fetch_queue.clone().into_iter().collect() }) 153 | .expect("Failed to request batch of chunks") 154 | } 155 | 156 | self.mesh = meshes; 157 | } 158 | 159 | pub fn insert_serialized_chunk(&mut self, chunk_x: i32, chunk_z: i32, serialized_chunk: String) { 160 | let chunk = ServerChunk::from_serialized(serialized_chunk, chunk_x, chunk_z); 161 | self.chunks.insert(chunk_x, chunk_z, chunk); 162 | self.chunk_fetch_queue.remove(&(chunk_x, chunk_z)); 163 | 164 | // when all chunks requested have been fetched re-render mesh 165 | if self.chunk_fetch_queue.len() == 0 { 166 | self.recalculate_mesh_from_perspective(self.player_chunk_x, self.player_chunk_z); 167 | } 168 | } 169 | 170 | pub fn get_chunk_mut(&mut self, chunk_x: i32, chunk_z: i32) -> Option<&mut ServerChunk> { 171 | match self.chunks.contains(chunk_x, chunk_z) { 172 | true => self.chunks.get_mut(chunk_x, chunk_z), 173 | false => None 174 | } 175 | } 176 | 177 | fn contains_chunk(&self, chunk_x: i32, chunk_z: i32) -> bool { 178 | self.chunks.contains(chunk_x, chunk_z) 179 | } 180 | 181 | pub fn set_block(&mut self, world_x: i32, world_y: i32, world_z: i32, block: BlockType) { 182 | let (chunk_x, chunk_z, local_x, local_z) = self.localize_coords_to_chunk(world_x, world_z); 183 | 184 | // set block 185 | { 186 | let chunk = self.get_chunk_mut(chunk_x, chunk_z); 187 | match chunk { 188 | Some(chunk) => chunk.set_block(local_x, world_y as usize, local_z, block), 189 | None => return 190 | } 191 | } 192 | 193 | // update chunk mesh 194 | self.update_chunk_mesh(chunk_x, chunk_z); 195 | if local_x == 0 { 196 | self.update_chunk_mesh(chunk_x - 1, chunk_z) 197 | } else if local_x == 15 { 198 | self.update_chunk_mesh(chunk_x + 1, chunk_z) 199 | } else if local_z == 0 { 200 | self.update_chunk_mesh(chunk_x, chunk_z - 1) 201 | } else if local_z == 15 { 202 | self.update_chunk_mesh(chunk_x, chunk_z + 1) 203 | } 204 | } 205 | 206 | fn update_chunk_mesh(&mut self, chunk_x: i32, chunk_z: i32) { 207 | // assume adjacent chunks exist 208 | let right_chunk = self.get_chunk(chunk_x + 1, chunk_z).unwrap(); 209 | let left_chunk = self.get_chunk(chunk_x - 1, chunk_z).unwrap(); 210 | let front_chunk = self.get_chunk(chunk_x, chunk_z + 1).unwrap(); 211 | let back_chunk = self.get_chunk(chunk_x, chunk_z - 1).unwrap(); 212 | let chunk = self.get_chunk(chunk_x, chunk_z).unwrap(); 213 | let mesh = chunk.gen_mesh(right_chunk, left_chunk, front_chunk, back_chunk); 214 | 215 | self.get_chunk_mut(chunk_x, chunk_z).unwrap().mesh = mesh; 216 | } 217 | 218 | pub fn get_chunk(&self, chunk_x: i32, chunk_z: i32) -> Option<&ServerChunk> { 219 | self.chunks.get(chunk_x, chunk_z) 220 | } 221 | 222 | pub fn localize_coords_to_chunk(&self, world_x: i32, world_z: i32) -> (i32, i32, usize, usize) { 223 | let mut chunk_x = (world_x + if world_x < 0 { 1 } else { 0 }) / 16; 224 | if world_x < 0 { 225 | chunk_x -= 1; 226 | } 227 | 228 | let mut chunk_z = (world_z + if world_z < 0 { 1 } else { 0 }) / 16; 229 | if world_z < 0 { 230 | chunk_z -= 1; 231 | } 232 | 233 | let local_x = ((chunk_x.abs() * 16 + world_x) % 16).abs() as usize; 234 | let local_z = ((chunk_z.abs() * 16 + world_z) % 16).abs() as usize; 235 | (chunk_x, chunk_z, local_x, local_z) 236 | } 237 | 238 | pub fn raymarch_block(&mut self, position: &Vector3, direction: &Vector3) -> Option<((i32, i32, i32), Option)> { 239 | let mut check_position = *position; 240 | let dir: Vector3 = *direction / 10.0; 241 | let mut range = 250; 242 | 243 | let mut result = Vec::new(); 244 | loop { 245 | check_position = check_position + dir; 246 | let x = check_position.x.round() as i32; 247 | let y = check_position.y.round() as i32; 248 | let z = check_position.z.round() as i32; 249 | result.push((x, y, z)); 250 | 251 | let block = self.get_block(x, y, z); 252 | if let Some(block) = block { 253 | if block != BlockType::Air && block != BlockType::Water { 254 | let vector = (*position - (check_position - dir)).normalize(); 255 | let abs_x = vector.x.abs(); 256 | let abs_y = vector.y.abs(); 257 | let abs_z = vector.z.abs(); 258 | let mut face = None; 259 | 260 | let mut face_is_x = false; 261 | // get cube face from ray direction 262 | // negated ray is on x-axis 263 | let sign = signum(vector.x); 264 | if self.moveable(x + sign, y, z) { 265 | face = if vector.x > 0.0 { 266 | Some(Face::Right) 267 | } else { 268 | Some(Face::Left) 269 | }; 270 | face_is_x = true; 271 | } 272 | 273 | if face.is_none() || abs_y > abs_x { 274 | // negated ray is on y-axis 275 | let sign = signum(vector.y); 276 | if self.moveable(x, y + sign, z) { 277 | face = if vector.y > 0.0 { 278 | Some(Face::Top) 279 | } else { 280 | Some(Face::Bottom) 281 | }; 282 | face_is_x = false; 283 | } 284 | } 285 | 286 | let sign = signum(vector.z); 287 | if face.is_none() || if face_is_x { abs_z > abs_x } else { abs_z > abs_y } { 288 | // negated ray is on z-axis 289 | //let sign = signum(vector.z); 290 | if self.moveable(x, y, z + sign) { 291 | face = if vector.z > 0.0 { 292 | Some(Face::Back) 293 | } else { 294 | Some(Face::Front) 295 | } 296 | } 297 | } 298 | 299 | return Some(((x, y, z), face)); 300 | } 301 | } 302 | 303 | if range == 0 { 304 | return None; 305 | } 306 | range = range - 1; 307 | } 308 | } 309 | } 310 | 311 | fn signum(n: f32) -> i32 { 312 | if n > 0.0 { 313 | 1 314 | } else { 315 | -1 316 | } 317 | } -------------------------------------------------------------------------------- /src/main.rs: -------------------------------------------------------------------------------- 1 | // modules 2 | mod core; 3 | mod multiplayer; 4 | mod opengl; 5 | mod traits; 6 | mod utils; 7 | 8 | // imports 9 | use std::{fs, path::Path, sync::{Arc, Mutex, mpsc::Receiver}, time::Instant}; 10 | use cgmath::{Deg, Matrix4, Vector3}; 11 | use glutin::{ContextBuilder, dpi::{PhysicalPosition, PhysicalSize}, event::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}, event_loop::{ControlFlow, EventLoop}, window::WindowBuilder}; 12 | use image::GenericImage; 13 | use noise::OpenSimplex; 14 | use gl::types::*; 15 | use rand::Rng; 16 | use crate::{core::{block_type::{BlockType, index_to_block}, face::Face, player::Player, window_mode::WindowMode, world::World}, multiplayer::{rc_message::RustyCraftMessage, server_connection::ServerConnection, server_state::ServerState, server_world::ServerWorld}, opengl::{button::Button, camera::Camera, cloud::Cloud, input::Input, player_model::PlayerModel, shader::Shader, tex_quad::TexQuad, text_renderer::{TextJustification, TextRenderer}, texture::Texture, vertex_array::VertexArray, vertex_buffer::VertexBuffer}, traits::game_world::GameWorld, utils::{name_utils::gen_name, num_utils::distance, simplex_utils::sample}}; 17 | 18 | // settings 19 | const SCR_WIDTH: u32 = 1000; 20 | const SCR_HEIGHT: u32 = 600; 21 | 22 | const SERVER_RENDER_DISTANCE: u32 = 10; 23 | const LOCAL_RENDER_DISTANCE: u32 = 20; 24 | 25 | fn main() { 26 | // unsafe { println!("{:?}", gl::GetString(gl::VERSION)); } 27 | // wrap program in helper 28 | // for unsafe block w/o indentation 29 | unsafe { start(); } 30 | } 31 | 32 | unsafe fn start() { 33 | // glfw: initialize 34 | let el = EventLoop::new(); 35 | let wb = WindowBuilder::new().with_title("A fantastic window!"); 36 | 37 | let windowed_context = ContextBuilder::new().build_windowed(wb, &el).unwrap(); 38 | 39 | let windowed_context = unsafe { windowed_context.make_current().unwrap() }; 40 | 41 | println!("Pixel format of the window's GL context: {:?}", windowed_context.get_pixel_format()); 42 | 43 | // set window icon; note that on MacOS this does nothing 44 | // as the icon must be set via .app bundling 45 | 46 | // capture mouse 47 | let mut mouse_captured = true; 48 | 49 | // gl: load all OpenGL function pointers 50 | gl::load_with(|symbol| windowed_context.get_proc_address(symbol) as *const _); 51 | 52 | // depth buffer 53 | gl::Enable(gl::DEPTH_TEST); 54 | 55 | // face culling 56 | //gl::Enable(gl::CULL_FACE); 57 | 58 | // blending 59 | gl::Enable(gl::BLEND); 60 | gl::BlendFunc(gl::SRC_ALPHA, gl::ONE_MINUS_SRC_ALPHA); 61 | 62 | let shader = Shader::new_with_geom("assets/shaders/voxal/vertex.vert", "assets/shaders/voxal/fragment.frag", "assets/shaders/voxal/geometry.geom"); 63 | 64 | // create vertex array 65 | let vao = VertexArray::new(); 66 | vao.bind(); 67 | 68 | // create VBO 69 | let mut vbo = VertexBuffer::new(); 70 | vbo.bind(); 71 | 72 | // set vertex attribute pointers 73 | // position 74 | vbo.add_float_attribute(3, 10); 75 | for _ in 0..6 { 76 | // block indices attributes 77 | vbo.add_float_attribute(1, 10); 78 | } 79 | // faces to draw via bitwise 80 | vbo.add_float_attribute(1, 10); 81 | 82 | let texture_map = Texture::new( 83 | "assets/textures/textures.png", 84 | gl::TEXTURE0, 85 | false 86 | ); 87 | 88 | let mut player = Player::new(SCR_WIDTH, SCR_HEIGHT); 89 | 90 | let mut instant = Instant::now(); 91 | 92 | // last mouse x and y coords 93 | let mut last_x: f32 = 400.0; 94 | let mut last_y: f32 = 300.0; 95 | 96 | // store if the mouse has entered the window to prevent 97 | // initially yanking the camera 98 | let mut first_mouse = true; 99 | 100 | // init text renderer 101 | let text_renderer = TextRenderer::new(SCR_WIDTH, SCR_HEIGHT, "assets/font/OldSchoolAdventures.ttf"); 102 | 103 | // target fps 104 | let target_fps = 60.0; 105 | 106 | let mut selected_coords = None; 107 | 108 | let mut force_recalculation = false; 109 | 110 | let mut current_block_index = 0; 111 | 112 | let water_tint_quad = TexQuad::new("assets/textures/water.png", gl::TEXTURE0, true, SCR_WIDTH, SCR_HEIGHT); 113 | 114 | let mut menu_world = World::new_with_seed(10, "menu_world", 0); 115 | 116 | let mut menu_camera = Camera::new(SCR_WIDTH, SCR_HEIGHT, 0.0); 117 | menu_world.recalculate_mesh_from_perspective(0, 0); 118 | menu_camera.position.y = menu_world.highest_in_column(0, 0).unwrap() as f32 + 10.0; 119 | menu_camera.fov = 60.0; 120 | menu_camera.mouse_callback(0.0, -140.0); 121 | 122 | let mut yellow_text_size: f32 = 0.0; 123 | 124 | // GUI Widgets 125 | // ---------- 126 | // buttons 127 | let button_width = 420.0; 128 | let button_height = 48.0; 129 | let button_x = SCR_WIDTH as f32 / 2.0; 130 | let select_worlds_button = Button::new("Open World", button_x, 280.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT); 131 | let open_world_button = Button::new("Open", button_x, 210.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT); 132 | let connect_button = Button::new("Connect", button_x, 140.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT); 133 | let mut back_button = Button::new("Back", button_x, 60.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT); 134 | let connect_to_server_button = Button::new("Connect to Server", button_x, 210.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT); 135 | 136 | // inputs 137 | let mut open_world_input = Input::new(button_x, 280.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT, 1.0, TextJustification::Center); 138 | let mut connect_to_server_input = Input::new(button_x, 210.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT, 1.0, TextJustification::Center); 139 | let mut server_player_name_input = Input::new(button_x, 280.0, button_width, button_height, SCR_WIDTH, SCR_HEIGHT, 1.0, TextJustification::Center); 140 | let mut chat_input = Input::new(SCR_WIDTH as f32 / 2.0, 30.0, SCR_WIDTH as f32 + 30.0, button_height / 1.3, SCR_WIDTH, SCR_HEIGHT, 0.8, TextJustification::Left); 141 | 142 | let last_world = fs::read_to_string("game_data/last_world"); 143 | if last_world.is_ok() { 144 | open_world_input.text = last_world.unwrap(); 145 | } 146 | 147 | let last_server = fs::read_to_string("game_data/last_server"); 148 | if last_server.is_ok() { 149 | connect_to_server_input.text = last_server.unwrap(); 150 | } 151 | 152 | let player_name = fs::read_to_string("game_data/player_name"); 153 | if player_name.is_ok() { 154 | server_player_name_input.text = player_name.unwrap(); 155 | } else { 156 | server_player_name_input.text = gen_name(); 157 | } 158 | 159 | // window 160 | let mut window_mode = WindowMode::Title; 161 | 162 | // placeholder world object 163 | let mut world = None; 164 | let mut server_connection = None; 165 | let mut server_state = None; 166 | let mut did_just_fail_to_connect = false; 167 | let mut shift_pressed = false; 168 | let mut time = 0.01; 169 | let mut server_chat_opened = false; 170 | let mut last_position_before_update_packet = Vector3::new(0.0, 0.0, 0.0); 171 | let mut update_position_packet = Instant::now(); 172 | 173 | // player model object 174 | let player_model = PlayerModel::new("assets/textures/player_skin.png"); 175 | 176 | // cloud model 177 | let cloud = Cloud::new(); 178 | let cloud_simplex = OpenSimplex::new(); 179 | let mut cloud_z_offset = 0.0; 180 | 181 | let mut show_gui = true; 182 | 183 | let subtitle_text = get_subtitle_text(); 184 | 185 | let mut should_close = false; 186 | 187 | // render loop 188 | el.run(move |event, _, control_flow| { 189 | if should_close { 190 | *control_flow = ControlFlow::Exit; 191 | } else { 192 | *control_flow = ControlFlow::Wait; 193 | } 194 | 195 | let deltatime = instant.elapsed().as_millis() as f32; 196 | instant = Instant::now(); 197 | time += 0.01; 198 | 199 | // bind framebuffer 200 | //framebuffer.bind(); 201 | 202 | cloud_z_offset += 0.01; 203 | 204 | let PhysicalSize { width: screen_width, height: screen_height } = windowed_context.window().inner_size(); 205 | 206 | // clear buffers 207 | gl::ClearColor(29.0 / 255.0, 104.0 / 255.0, 224.0 / 255.0, 1.0); 208 | //gl::ClearColor(0.0 / 255.0, 0.0 / 255.0, 0.0 / 255.0, 1.0); 209 | gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); 210 | gl::Enable(gl::DEPTH_TEST); 211 | match window_mode { 212 | WindowMode::Title | WindowMode::OpenWorld | WindowMode::ConnectToServer => { 213 | match event { 214 | Event::WindowEvent { event, .. } => match event { 215 | WindowEvent::Resized(PhysicalSize { width, height }) => { 216 | gl::Viewport(0, 0, width as i32, height as i32) 217 | }, 218 | WindowEvent::CursorMoved { position: PhysicalPosition { x, y }, .. } => { 219 | last_x = x as f32; 220 | last_y = y as f32; 221 | }, 222 | WindowEvent::MouseInput { state: ElementState::Pressed, .. } => { 223 | let last_y = SCR_HEIGHT as f32 - last_y; 224 | match window_mode { 225 | WindowMode::Title => { 226 | if select_worlds_button.is_hovered(last_x, last_y, screen_width, screen_height) { 227 | window_mode = WindowMode::OpenWorld; 228 | } 229 | 230 | if connect_to_server_button.is_hovered(last_x, last_y, screen_width, screen_height) { 231 | window_mode = WindowMode::ConnectToServer; 232 | } 233 | }, 234 | WindowMode::OpenWorld => { 235 | open_world_input.update_focus(last_x, last_y); 236 | if back_button.is_hovered(last_x, last_y, screen_width, screen_height) { 237 | window_mode = WindowMode::Title; 238 | } 239 | 240 | if open_world_button.is_hovered(last_x, last_y, screen_width, screen_height) { 241 | let mut world_object = World::new(LOCAL_RENDER_DISTANCE, open_world_input.text.clone().as_str()); 242 | world_object.recalculate_mesh_from_perspective(0, 0); 243 | let last_player_pos = fs::read_to_string(format!("game_data/worlds/{}/player_pos", open_world_input.text.clone().as_str())); 244 | if let Ok(player_pos_str) = last_player_pos { 245 | let words: Vec<&str> = player_pos_str.split(" ").collect(); 246 | let x = words[0].parse::(); 247 | let y = words[1].parse::(); 248 | let z = words[2].parse::(); 249 | 250 | if let Ok(x) = x { 251 | player.camera.position.x = x; 252 | } 253 | 254 | if let Ok(y) = y { 255 | player.camera.position.y = y; 256 | } 257 | 258 | if let Ok(z) = z { 259 | player.camera.position.z = z; 260 | } 261 | } else { 262 | player.camera.position.y = world_object.highest_in_column(0, 0).unwrap() as f32 + 2.0; 263 | } 264 | 265 | world = Some(world_object); 266 | window_mode = WindowMode::InWorld; 267 | windowed_context.window().set_cursor_visible(false); 268 | current_block_index = 0; 269 | fs::write("game_data/last_world", open_world_input.text.clone()) 270 | .expect("Failed to write world input text to file"); 271 | } 272 | }, 273 | WindowMode::ConnectToServer => { 274 | connect_to_server_input.update_focus(last_x, last_y); 275 | server_player_name_input.update_focus(last_x, last_y); 276 | if connect_button.is_hovered(last_x, last_y, screen_width, screen_height) { 277 | let address = connect_to_server_input.text.clone(); 278 | let connection = ServerConnection::new(address.clone()); 279 | match connection { 280 | Err(_) => { 281 | did_just_fail_to_connect = true; 282 | }, 283 | Ok(connection) => { 284 | let mut world = ServerWorld::new(SERVER_RENDER_DISTANCE, connection.clone()); 285 | world.recalculate_mesh_from_perspective(0, 0); 286 | let world = Arc::new(Mutex::new(world)); 287 | server_state = Some(ServerState::new(world.clone())); 288 | connection.clone().send_message(RustyCraftMessage::PlayerJoin { name: String::from(server_player_name_input.text.clone()) }) 289 | .expect("Failed to set name on join"); 290 | connection.clone().create_listen_thread(server_state.clone().unwrap()); 291 | server_connection = Some(connection.clone()); 292 | windowed_context.window().set_cursor_visible(true); 293 | window_mode = WindowMode::InServer; 294 | fs::write("game_data/last_server", address.clone()) 295 | .expect("Failed to write world input text to file"); 296 | fs::write("game_data/player_name", server_player_name_input.text.clone()) 297 | .expect("Failed to write world input text to file"); 298 | did_just_fail_to_connect = false; 299 | current_block_index = 0; 300 | } 301 | } 302 | } 303 | 304 | if back_button.is_hovered(last_x, last_y, screen_width, screen_height) { 305 | window_mode = WindowMode::Title; 306 | did_just_fail_to_connect = false; 307 | } 308 | }, 309 | _ => () 310 | } 311 | }, 312 | WindowEvent::KeyboardInput { input: KeyboardInput { state: ElementState::Pressed, virtual_keycode: Some(keycode), .. }, .. } => { 313 | match keycode { 314 | VirtualKeyCode::Escape => should_close = true, 315 | // VirtualKeyCode::Escape => should_pressed = true, 316 | _ => { 317 | match window_mode { 318 | WindowMode::OpenWorld => { 319 | open_world_input.type_key(keycode, shift_pressed, &text_renderer); 320 | }, 321 | WindowMode::ConnectToServer => { 322 | connect_to_server_input.type_key(keycode, shift_pressed, &text_renderer); 323 | server_player_name_input.type_key(keycode, shift_pressed, &text_renderer); 324 | }, 325 | _ => () 326 | } 327 | } 328 | } 329 | }, 330 | _ => () 331 | }, 332 | _ => () 333 | } 334 | 335 | // if window mode was changed to InWorld: 336 | if window_mode == WindowMode::InWorld || window_mode == WindowMode::InServer { 337 | continue; 338 | } 339 | 340 | // text_renderer.render_text("Create World", x + 20.0, 200.0, 1.0, Vector3::new(1.0, 0.0, 0.0)); 341 | // button.draw(x, 180.0, x + 200.0, 230.0, 1.0); 342 | 343 | yellow_text_size += 0.075; 344 | 345 | gl::Clear(gl::COLOR_BUFFER_BIT | gl::DEPTH_BUFFER_BIT); 346 | 347 | // shader uniforms 348 | shader.use_program(); 349 | shader.set_mat4("view", menu_camera.get_view()); 350 | shader.set_mat4("projection", menu_camera.get_projection()); 351 | shader.set_mat4("model", Matrix4::::from_scale(1.0)); 352 | shader.set_vec3("light_pos", Vector3::new(menu_camera.position.x, menu_camera.position.y - 2.0, menu_camera.position.z)); 353 | shader.set_vec3("view_pos", menu_camera.position); 354 | shader.set_float("time", time); 355 | 356 | menu_camera.mouse_callback(0.15, 0.0); 357 | 358 | // bind texture 359 | texture_map.bind(); 360 | shader.set_uint("texture_map", 0); 361 | 362 | // draw 363 | vao.bind(); 364 | vbo.bind(); 365 | 366 | let meshes = menu_world.get_world_mesh_from_perspective(0, 0, false); 367 | // opaque block points 368 | for mesh in meshes.iter() { 369 | vbo.set_data(&mesh.0, gl::DYNAMIC_DRAW); 370 | gl::DrawArrays(gl::POINTS, 0, (mesh.0.len() / 10) as GLint); 371 | } 372 | 373 | // transparent block points 374 | for mesh in meshes.iter() { 375 | vbo.set_data(&mesh.1, gl::DYNAMIC_DRAW); 376 | gl::DrawArrays(gl::POINTS, 0, (mesh.1.len() / 10) as GLint); 377 | } 378 | 379 | // text 380 | let x = (SCR_WIDTH / 2) as f32; 381 | let y = SCR_HEIGHT as f32 - 220.0; 382 | let subtitle_size = 0.7 + (yellow_text_size.sin() + 1.0) / 30.0; 383 | text_renderer.render_text_with_mat(subtitle_text.as_str(), 870.0 - subtitle_size * 100.0, y - 130.0, subtitle_size, Vector3::new(1.0, 1.0, 0.0), Matrix4::::from_angle_z(Deg(10.0)), TextJustification::Center); 384 | text_renderer.render_text("RustyCraft", x, y, 3.5, Vector3::new(1.0, 0.0, 0.0), TextJustification::Center); 385 | text_renderer.render_text("RustyCraft", x + 3.0, y - 3.0, 3.5, Vector3::new(173.0 / 255.0, 24.0 / 255.0, 24.0 / 255.0), TextJustification::Center); 386 | text_renderer.render_text("v1.0", SCR_WIDTH as f32 - 60.0, 10.0, 0.9, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 387 | 388 | let last_y = SCR_HEIGHT as f32 - last_y; 389 | match window_mode { 390 | WindowMode::Title => { 391 | select_worlds_button.draw(&text_renderer, last_x, last_y, screen_width, screen_height); 392 | connect_to_server_button.draw(&text_renderer, last_x, last_y, screen_width, screen_height); 393 | }, 394 | WindowMode::OpenWorld => { 395 | back_button.set_y(140.0); 396 | open_world_button.draw(&text_renderer, last_x, last_y, screen_width, screen_height); 397 | open_world_input.draw(&text_renderer); 398 | back_button.draw(&text_renderer, last_x, last_y, screen_width, screen_height); 399 | }, 400 | WindowMode::ConnectToServer => { 401 | back_button.set_y(70.0); 402 | connect_button.draw(&text_renderer, last_x, last_y, screen_width, screen_height); 403 | connect_to_server_input.draw(&text_renderer); 404 | server_player_name_input.draw(&text_renderer); 405 | back_button.draw(&text_renderer, last_x, last_y, screen_width, screen_height); 406 | if did_just_fail_to_connect { 407 | text_renderer.render_text("Failed to Connect", button_x - 210.0, 310.0, 1.0, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 408 | } 409 | }, 410 | _ => panic!("Attempted to display window mode that was not a title menu") 411 | }; 412 | }, 413 | WindowMode::InWorld => { 414 | let mut world = world.as_mut().unwrap(); 415 | 416 | // events 417 | process_events( 418 | &mut window, 419 | &events, 420 | &mut show_gui, 421 | &mut mouse_captured, 422 | &selected_coords, 423 | &mut world, 424 | &mut player, 425 | &mut last_x, 426 | &mut last_y, 427 | &mut first_mouse, 428 | &mut force_recalculation, 429 | &mut current_block_index, 430 | &mut window_mode 431 | ); 432 | 433 | player.update_position(world, deltatime); 434 | 435 | // update player altitude 436 | player.update_alt(world); 437 | 438 | // draw clouds 439 | render_clouds(&cloud, &player.camera, &player.camera.position, cloud_z_offset, cloud_simplex); 440 | 441 | if show_gui { 442 | // draw text 443 | text_renderer.render_text(format!("FPS: {}", (1000.0 / deltatime).round()).as_str(), 10.0, (SCR_HEIGHT as f32) - 30.0, 1.0, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 444 | text_renderer.render_text(format!("x: {:.2}", player.camera.position.x).as_str(), 10.0, (SCR_HEIGHT as f32) - 50.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 445 | text_renderer.render_text(format!("y: {:.2}", player.camera.position.y).as_str(), 10.0, (SCR_HEIGHT as f32) - 70.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 446 | text_renderer.render_text(format!("z: {:.2}", player.camera.position.z).as_str(), 10.0, (SCR_HEIGHT as f32) - 90.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 447 | 448 | let block = index_to_block(current_block_index).unwrap(); 449 | text_renderer.render_text(format!("Selected block: {:?}", block).as_str(), 10.0, (SCR_HEIGHT as f32) - 110.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 450 | } 451 | 452 | // shader uniforms 453 | shader.use_program(); 454 | // transforms 455 | shader.set_mat4("view", player.camera.get_view()); 456 | shader.set_mat4("projection", player.camera.get_projection()); 457 | shader.set_mat4("model", Matrix4::::from_scale(1.0)); 458 | shader.set_vec3("light_pos", player.camera.position); 459 | shader.set_float("time", time); 460 | 461 | // bind texture 462 | texture_map.bind(); 463 | shader.set_texture("texture_map", &texture_map); 464 | 465 | // draw 466 | vao.bind(); 467 | vbo.bind(); 468 | 469 | let meshes = world.get_world_mesh_from_perspective(player.camera.position.x as i32, player.camera.position.z as i32, force_recalculation); 470 | force_recalculation = false; 471 | // opaque block points 472 | for mesh in meshes.iter() { 473 | vbo.set_data(&mesh.0, gl::DYNAMIC_DRAW); 474 | gl::DrawArrays(gl::POINTS, 0, (mesh.0.len() / 10) as GLint); 475 | } 476 | 477 | // transparent block points 478 | for mesh in meshes.iter() { 479 | vbo.set_data(&mesh.1, gl::DYNAMIC_DRAW); 480 | gl::DrawArrays(gl::POINTS, 0, (mesh.1.len() / 10) as GLint); 481 | } 482 | 483 | selected_coords = world.raymarch_block(&player.camera.position, &player.camera.front); 484 | if let Some(((x, y, z), Some(face))) = selected_coords { 485 | if show_gui { 486 | draw_block_selector(x, y, z, face, &shader, &vbo); 487 | } 488 | } 489 | 490 | // couldn't get framebuffer to work for post-processing 491 | // so draw a blue textured transparent quad for underwater 492 | // effect now 493 | if player.underwater(world) { 494 | player.camera.speed = 0.003; 495 | water_tint_quad.draw(0.0, 0.0, SCR_WIDTH as f32, SCR_HEIGHT as f32, 0.7); 496 | } else { 497 | if player.camera.speed < 0.008 { 498 | player.camera.speed = 0.008; 499 | } 500 | } 501 | }, 502 | WindowMode::InServer => { 503 | // assume server connection must be Some 504 | let connection = server_connection.as_mut().unwrap(); 505 | let state = server_state.clone().unwrap(); 506 | let server_world = state.world; 507 | 508 | for (_, event) in glfw::flush_messages(&events) { 509 | match event { 510 | WindowEvent::FramebufferSize(width, height) => { 511 | gl::Viewport(0, 0, width, height); 512 | }, 513 | WindowEvent::Scroll(_, y_offset) => { 514 | player.camera.scroll_callback(y_offset as f32); 515 | }, 516 | WindowEvent::CursorPos(xpos, ypos) => { 517 | let (x_pos, y_pos) = (xpos as f32, ypos as f32); 518 | let x_offset = x_pos - last_x; 519 | let y_offset = last_y - y_pos; 520 | last_x = x_pos; 521 | last_y = y_pos; 522 | 523 | if mouse_captured { 524 | player.camera.mouse_callback(x_offset, y_offset); 525 | connection.send_message(RustyCraftMessage::PlayerDirection { 526 | yaw: player.camera.yaw, 527 | pitch: player.camera.pitch 528 | }).expect("Failed to send movement packet"); 529 | } 530 | }, 531 | WindowEvent::MouseButton(MouseButton::Button1, Action::Press, _) => { 532 | if mouse_captured { 533 | if let Some(((x, y, z), _)) = selected_coords { 534 | connection.send_message(RustyCraftMessage::SetBlock { world_x: x, world_y: y, world_z: z, block: BlockType::Air }) 535 | .expect("Failed to send SetBlock packets"); 536 | } 537 | } 538 | }, 539 | WindowEvent::MouseButton(MouseButton::Button2, Action::Press, _) => { 540 | if mouse_captured { 541 | if let Some(((x, y, z), Some(face))) = selected_coords { 542 | let place_position = get_block_on_face(x, y, z, &face); 543 | if can_place_block_at_loc(player.camera.position, place_position.0, place_position.1, place_position.2) { 544 | let block = index_to_block(current_block_index); 545 | connection.send_message(RustyCraftMessage::SetBlock { world_x: place_position.0, world_y: place_position.1, world_z: place_position.2, block: block.unwrap() }) 546 | .expect("Failed to send SetBlock packets"); 547 | } 548 | } 549 | } 550 | }, 551 | WindowEvent::Key(Key::Escape, _, Action::Press, _) => { 552 | if server_chat_opened { 553 | server_chat_opened = false; 554 | shift_pressed = false; 555 | mouse_captured = true; 556 | window.set_cursor_mode(CursorMode::Disabled); 557 | chat_input.set_focus(false); 558 | } else { 559 | window_mode = WindowMode::Title; 560 | window.set_cursor_mode(CursorMode::Normal); 561 | connection.send_message(RustyCraftMessage::Disconnect) 562 | .expect("Failed to send disconnect message"); 563 | } 564 | }, 565 | WindowEvent::Key(Key::F3, _, Action::Press, _) => player.toggle_camera(), 566 | WindowEvent::Key(Key::F1, _, Action::Press, _) => show_gui = !show_gui, 567 | WindowEvent::Key(Key::LeftShift, _, Action::Press, _) if server_chat_opened => shift_pressed = true, 568 | WindowEvent::Key(Key::LeftShift, _, Action::Release, _) if server_chat_opened => shift_pressed = false, 569 | WindowEvent::Key(Key::Enter, _, Action::Press, _) if server_chat_opened => { 570 | connection.send_message(RustyCraftMessage::ChatMessage { content: chat_input.text.clone() }) 571 | .expect("Failed to send chat message"); 572 | chat_input.text = String::new(); 573 | shift_pressed = false; 574 | server_chat_opened = false; 575 | window.set_cursor_mode(CursorMode::Disabled); 576 | mouse_captured = true; 577 | }, 578 | WindowEvent::Key(key, _, Action::Press, _) if server_chat_opened => { 579 | chat_input.type_key(key, shift_pressed, &text_renderer); 580 | }, 581 | WindowEvent::Key(Key::T, _, Action::Press, _) => { 582 | mouse_captured = false; 583 | server_chat_opened = true; 584 | window.set_cursor_mode(CursorMode::Normal); 585 | chat_input.set_focus(true); 586 | }, 587 | WindowEvent::Key(Key::LeftSuper, _, Action::Press, _) => { 588 | mouse_captured = !mouse_captured; 589 | window.set_cursor_mode(match mouse_captured { 590 | true => CursorMode::Disabled, 591 | false => CursorMode::Normal 592 | }); 593 | }, 594 | WindowEvent::Key(Key::Up, _, Action::Press, _) => { 595 | if index_to_block(current_block_index + 1).is_some() { 596 | current_block_index += 1 597 | } 598 | }, 599 | WindowEvent::Key(Key::Down, _, Action::Press, _) => { 600 | if current_block_index > 0 { 601 | current_block_index -= 1 602 | } 603 | }, 604 | WindowEvent::Key(Key::Space, _, Action::Press, _) => player.jump(), 605 | WindowEvent::Key(Key::LeftShift, _, Action::Press, _) => player.camera.speed = 0.05, 606 | WindowEvent::Key(Key::LeftShift, _, Action::Release, _) => player.camera.speed = 0.008, 607 | WindowEvent::Key(key, _, action, _) => player.camera.process_keyboard(key, action), 608 | _ => () 609 | } 610 | } 611 | 612 | // continue if window mode was changed 613 | if window_mode == WindowMode::Title { 614 | continue; 615 | } 616 | let position = player.camera.position; 617 | 618 | player.update_position(&*server_world.lock().unwrap(), deltatime); 619 | 620 | // update player altitude 621 | player.update_alt(&*server_world.lock().unwrap()); 622 | 623 | // draw clouds 624 | render_clouds(&cloud, &player.camera, &player.camera.position, cloud_z_offset, cloud_simplex); 625 | 626 | if show_gui { 627 | // draw text 628 | text_renderer.render_text(format!("Connected to {}", connection.address).as_str(), 10.0, (SCR_HEIGHT as f32) - 30.0, 1.0, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 629 | text_renderer.render_text(format!("FPS: {}", (1000.0 / deltatime).round()).as_str(), 10.0, (SCR_HEIGHT as f32) - 60.0, 1.0, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 630 | text_renderer.render_text(format!("x: {:.2}", player.camera.position.x).as_str(), 10.0, (SCR_HEIGHT as f32) - 80.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 631 | text_renderer.render_text(format!("y: {:.2}", player.camera.position.y).as_str(), 10.0, (SCR_HEIGHT as f32) - 100.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 632 | text_renderer.render_text(format!("z: {:.2}", player.camera.position.z).as_str(), 10.0, (SCR_HEIGHT as f32) - 120.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 633 | 634 | let block = index_to_block(current_block_index).unwrap(); 635 | text_renderer.render_text(format!("Selected block: {:?}", block).as_str(), 10.0, (SCR_HEIGHT as f32) - 140.0, 0.6, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 636 | 637 | // chat 638 | let chat = state.chat_stack.lock().unwrap(); 639 | for i in 0..chat.len().min(10) { 640 | let message = chat[chat.len() - 1 - i].as_str(); 641 | text_renderer.render_text(message, 10.0, (i as f32) * 20.0 + 60.0, 0.65, Vector3::new(1.0, 1.0, 1.0), TextJustification::Left); 642 | } 643 | } 644 | 645 | if server_chat_opened { 646 | chat_input.draw(&text_renderer); 647 | } 648 | 649 | // player models 650 | let client_id = state.client_id.lock().unwrap().clone(); 651 | for (_, p) in state.players.lock().unwrap().iter() { 652 | if p.id != client_id { 653 | player_model.draw(&player.camera, p.position, p.pitch, p.yaw); 654 | } 655 | } 656 | 657 | // shader uniforms 658 | shader.use_program(); 659 | // transforms 660 | shader.set_mat4("view", player.camera.get_view()); 661 | shader.set_mat4("projection", player.camera.get_projection()); 662 | shader.set_mat4("model", Matrix4::::from_scale(1.0)); 663 | 664 | // bind texture 665 | texture_map.bind(); 666 | shader.set_texture("texture_map", &texture_map); 667 | 668 | // draw 669 | vao.bind(); 670 | vbo.bind(); 671 | 672 | let x = position.x.round() as i32; 673 | let z = position.z.round() as i32; 674 | 675 | let mut server_world = server_world.lock().unwrap(); 676 | let meshes = server_world.get_world_mesh_from_perspective(x, z, force_recalculation); 677 | force_recalculation = false; 678 | // opaque block points 679 | for mesh in meshes.iter() { 680 | vbo.set_data(&mesh.0, gl::DYNAMIC_DRAW); 681 | gl::DrawArrays(gl::POINTS, 0, (mesh.0.len() / 10) as GLint); 682 | } 683 | 684 | // transparent block points 685 | for mesh in meshes.iter() { 686 | vbo.set_data(&mesh.1, gl::DYNAMIC_DRAW); 687 | gl::DrawArrays(gl::POINTS, 0, (mesh.1.len() / 10) as GLint); 688 | } 689 | 690 | selected_coords = server_world.raymarch_block(&player.camera.position, &player.camera.front); 691 | if let Some(((x, y, z), Some(face))) = selected_coords { 692 | if show_gui { 693 | draw_block_selector(x, y, z, face, &shader, &vbo); 694 | } 695 | } 696 | 697 | // couldn't get framebuffer to work for post-processing 698 | // so draw a blue textured transparent quad for underwater 699 | // effect now 700 | if player.underwater(&*server_world) { 701 | player.camera.speed = 0.003; 702 | water_tint_quad.draw(0.0, 0.0, SCR_WIDTH as f32, SCR_HEIGHT as f32, 0.7); 703 | } else { 704 | if player.camera.speed < 0.008 { 705 | player.camera.speed = 0.008; 706 | } 707 | } 708 | 709 | // send position update packet at 20FPS if position changed 710 | if (update_position_packet.elapsed().as_millis() as f32) > (1000.0 / 20.0) { 711 | if player.camera.position != last_position_before_update_packet { 712 | let position = player.camera.position; 713 | connection.send_message(RustyCraftMessage::PlayerPosition { 714 | x: position.x, 715 | y: position.y, 716 | z: position.z 717 | }).expect("Failed to send movement packet"); 718 | last_position_before_update_packet = player.camera.position; 719 | } 720 | update_position_packet = Instant::now(); 721 | } 722 | } 723 | } 724 | 725 | // second pass, draw framebuffer quad 726 | // FrameBuffer::unbind(); 727 | // gl::ClearColor(1.0, 1.0, 1.0, 1.0); 728 | // gl::Clear(gl::COLOR_BUFFER_BIT); 729 | // framebuffer.draw(); 730 | 731 | windowed_context.swap_buffers().unwrap(); 732 | 733 | // hang thread for target FPS 734 | while (instant.elapsed().as_millis() as f32) < (1000.0 / target_fps) {} 735 | }); 736 | } 737 | 738 | fn process_events(window: &mut glfw::Window, events: &Receiver<(f64, glfw::WindowEvent)>, show_gui: &mut bool, mouse_captured: &mut bool, selected_coords: &Option<((i32, i32, i32), Option)>, world: &mut World, player: &mut Player, last_x: &mut f32, last_y: &mut f32, first_mouse: &mut bool, force_recalculation: &mut bool, current_block_index: &mut usize, window_mode: &mut WindowMode) { 739 | for (_, event) in glfw::flush_messages(events) { 740 | match event { 741 | WindowEvent::FramebufferSize(width, height) => { 742 | unsafe { gl::Viewport(0, 0, width, height) } 743 | }, 744 | WindowEvent::Scroll(_, y_offset) => { 745 | player.camera.scroll_callback(y_offset as f32); 746 | }, 747 | WindowEvent::CursorPos(xpos, ypos) => { 748 | let (x_pos, y_pos) = (xpos as f32, ypos as f32); 749 | let x_offset = x_pos - *last_x; 750 | let y_offset = *last_y - y_pos; 751 | *last_x = x_pos; 752 | *last_y = y_pos; 753 | if *first_mouse { 754 | *first_mouse = false; 755 | return; 756 | } 757 | 758 | if !*mouse_captured { 759 | return; 760 | } 761 | 762 | player.camera.mouse_callback(x_offset, y_offset); 763 | }, 764 | WindowEvent::Key(Key::F3, _, Action::Press, _) => player.toggle_camera(), 765 | WindowEvent::Key(Key::F1, _, Action::Press, _) => *show_gui = !*show_gui, 766 | // WindowEvent::Key(Key::F2, _, Action::Press, _) => { 767 | // let width = SCR_WIDTH; 768 | // let height = SCR_HEIGHT; 769 | // let mut data = vec![0u8; (width * height * 4) as usize].into_boxed_slice(); 770 | // unsafe { gl::ReadPixels(0, height as i32, width as i32, height as i32, gl::RGBA, gl::UNSIGNED_BYTE, data.as_mut_ptr() as *mut c_void); } 771 | 772 | // let image = RgbaImage::from_raw(width, height, data.to_vec()) 773 | // .expect("Unable to convert pixel array to RgbImage"); 774 | // image.save("screenshot.png").expect("Unable to write image to file"); 775 | // println!("Saved screenshot"); 776 | // }, 777 | WindowEvent::MouseButton(MouseButton::Button1, Action::Press, _) => { 778 | if let Some(((x, y, z), _)) = selected_coords { 779 | world.set_block(*x, *y, *z, BlockType::Air); 780 | *force_recalculation = true; 781 | //world.recalculate_mesh_from_perspective((camera.position.x as i32) % 16, (camera.position.z as i32) % 16); 782 | } 783 | }, 784 | WindowEvent::MouseButton(MouseButton::Button2, Action::Press, _) => { 785 | if let Some(((x, y, z), Some(face))) = selected_coords { 786 | let x = *x; 787 | let y = *y; 788 | let z = *z; 789 | 790 | let place_position = get_block_on_face(x, y, z, face); 791 | let block = index_to_block(*current_block_index); 792 | 793 | if !can_place_block_at_loc(player.camera.position, place_position.0, place_position.1, place_position.2) { 794 | return; 795 | } 796 | world.set_block(place_position.0, place_position.1, place_position.2, block.unwrap()); 797 | *force_recalculation = true; 798 | } 799 | }, 800 | WindowEvent::Key(Key::Space, _, Action::Press, _) => player.jump(), 801 | WindowEvent::Key(Key::Up, _, Action::Press, _) => { 802 | if index_to_block(*current_block_index + 1).is_some() { 803 | *current_block_index += 1 804 | } 805 | }, 806 | WindowEvent::Key(Key::Down, _, Action::Press, _) => if *current_block_index > 0 { *current_block_index -= 1 }, 807 | WindowEvent::Key(Key::LeftShift, _, Action::Press, _) => player.camera.speed = 0.05, 808 | WindowEvent::Key(Key::LeftShift, _, Action::Release, _) => player.camera.speed = 0.008, 809 | WindowEvent::Key(Key::Escape, _, Action::Press, _) => { 810 | *current_block_index = 0; 811 | *window_mode = WindowMode::Title; 812 | window.set_cursor_mode(CursorMode::Normal); 813 | fs::write(format!("{}/player_pos", world.save_dir).as_str(), format!("{} {} {}", player.camera.position.x, player.camera.position.y, player.camera.position.z)) 814 | .expect("Failed to write player position to file"); 815 | }, 816 | WindowEvent::Key(Key::LeftSuper, _, Action::Press, _) => { 817 | *mouse_captured = !*mouse_captured; 818 | window.set_cursor_mode(match *mouse_captured { 819 | true => CursorMode::Disabled, 820 | false => CursorMode::Normal 821 | }); 822 | }, 823 | WindowEvent::Key(key, _, action, _) => player.camera.process_keyboard(key, action), 824 | _ => () 825 | } 826 | } 827 | } 828 | 829 | unsafe fn draw_block_selector(x: i32, y: i32, z: i32, face: Face, shader: &Shader, vbo: &VertexBuffer) { 830 | let mut mesh = Vec::new(); 831 | mesh.push(x as f32); 832 | mesh.push(y as f32); 833 | mesh.push(z as f32); 834 | for _ in 0..6 { 835 | mesh.push(7.0); 836 | }; 837 | 838 | let face_to_draw = match face { 839 | Face::Front => 0b10000000, 840 | Face::Right => 0b01000000, 841 | Face::Back => 0b00100000, 842 | Face::Bottom => 0b00010000, 843 | Face::Left => 0b00001000, 844 | Face::Top => 0b00000100, 845 | }; 846 | mesh.push(face_to_draw as f32); 847 | vbo.set_data(&mesh, gl::DYNAMIC_DRAW); 848 | 849 | shader.set_mat4("model", Matrix4::from_scale(1.01)); 850 | gl::DrawArrays(gl::POINTS, 0, 1); 851 | } 852 | 853 | fn can_place_block_at_loc(player_position: Vector3, x: i32, y: i32, z: i32) -> bool { 854 | x != player_position.x.round() as i32 855 | || (y != player_position.y.round() as i32 856 | && y != player_position.y.round() as i32 - 1) 857 | || z != player_position.z.round() as i32 858 | } 859 | 860 | fn get_block_on_face(x: i32, y: i32, z: i32, face: &Face) -> (i32, i32, i32) { 861 | match face { 862 | Face::Top => (x, y + 1, z), 863 | Face::Bottom => (x, y - 1, z), 864 | Face::Right => (x + 1, y, z), 865 | Face::Left => (x - 1, y, z), 866 | Face::Front => (x, y, z - 1), 867 | Face::Back => (x, y, z + 1), 868 | } 869 | } 870 | 871 | fn get_subtitle_text() -> String { 872 | let mut rng = rand::thread_rng(); 873 | let s = match rng.gen_range(0..8) { 874 | 0 => "Bad Minecraft -- in Rust!", 875 | 1 => "Extra mutable aliasing!", 876 | 2 => "Unperformant, unreliable & unproductive!", 877 | 3 => "unsafe { Game::minecraft() }", 878 | 4 => "Make boredom subtype 'static!", 879 | 5 => "None.unwrap()", 880 | 6 => "The No. 1 Craft!", 881 | _ => "Golang bad!" 882 | }; 883 | String::from(s) 884 | } 885 | 886 | unsafe fn render_clouds(cloud: &Cloud, camera: &Camera, player_position: &Vector3, z_offset: f32, simplex: OpenSimplex) { 887 | let cloud_x = (player_position.x as i32) / 10; 888 | let cloud_z = (player_position.z as i32) / 10; 889 | for x in 0..LOCAL_RENDER_DISTANCE * 4 { 890 | for z in 0..LOCAL_RENDER_DISTANCE * 4 { 891 | let local_x = LOCAL_RENDER_DISTANCE as i32 * 2 - x as i32; 892 | let local_z = LOCAL_RENDER_DISTANCE as i32 * 2 - z as i32; 893 | let x = LOCAL_RENDER_DISTANCE as i32 * 2 - x as i32 + cloud_x; 894 | let z = LOCAL_RENDER_DISTANCE as i32 * 2 - z as i32 + cloud_z - z_offset as i32 / 10; 895 | if distance(0, 0, local_x, local_z) < 2.0 * LOCAL_RENDER_DISTANCE as f32 { 896 | let noise = sample(x as f32 / 4.0, z as f32 / 4.0, simplex); 897 | if noise > 0.5 { 898 | cloud.draw(camera, x as i32, 100, z as i32, z_offset); 899 | } 900 | } 901 | } 902 | } 903 | } 904 | --------------------------------------------------------------------------------