├── .gitignore ├── .travis.yml ├── Cargo.lock ├── Cargo.toml ├── LICENSE-APACHE ├── LICENSE-MIT ├── README.md ├── base ├── Cargo.toml └── src │ ├── gen │ ├── mod.rs │ ├── plant │ │ ├── mod.rs │ │ └── tree.rs │ └── world │ │ ├── biome.rs │ │ └── mod.rs │ ├── lib.rs │ ├── math │ ├── axial_point.rs │ ├── axial_traits.rs │ ├── axial_vector.rs │ ├── billboard.rs │ ├── dimension.rs │ ├── mod.rs │ └── random.rs │ ├── prop │ ├── mod.rs │ └── plant.rs │ └── world │ ├── chunk.rs │ ├── ground.rs │ ├── hex_pillar.rs │ ├── mod.rs │ ├── provider.rs │ └── world.rs ├── ci ├── check-basic-style.sh ├── check-rustfmt.sh ├── install-tools.sh ├── run-all.sh ├── test-all.sh └── upload-docs.sh ├── client ├── Cargo.toml ├── shader │ ├── adaption_shrink.frag │ ├── adaption_shrink.vert │ ├── advancedtonemapping.frag │ ├── advancedtonemapping.vert │ ├── blend.frag │ ├── blend.vert │ ├── bloom_blending.frag │ ├── bloom_blending.vert │ ├── bloom_blur.frag │ ├── bloom_blur.vert │ ├── bloom_filter.frag │ ├── bloom_filter.vert │ ├── chunk_shadow.frag │ ├── chunk_shadow.vert │ ├── chunk_std.frag │ ├── chunk_std.vert │ ├── outline.frag │ ├── outline.vert │ ├── plant_shadow.frag │ ├── plant_shadow.vert │ ├── plants.frag │ ├── plants.tcs │ ├── plants.tes │ ├── plants.vert │ ├── plants_notess.frag │ ├── plants_notess.vert │ ├── relative_luminance.frag │ ├── relative_luminance.vert │ ├── shadow_debug.frag │ ├── shadow_debug.vert │ ├── skydome.frag │ ├── skydome.vert │ ├── sun.frag │ ├── sun.vert │ ├── tonemapping.frag │ ├── tonemapping.vert │ ├── weather.frag │ └── weather.vert └── src │ ├── camera.rs │ ├── config.rs │ ├── control_switcher.rs │ ├── daytime.rs │ ├── event_manager.rs │ ├── frustum.rs │ ├── game.rs │ ├── game_context.rs │ ├── ghost.rs │ ├── lib.rs │ ├── player.rs │ ├── renderer.rs │ ├── util │ ├── mod.rs │ └── to_arr.rs │ ├── view │ ├── mod.rs │ ├── plant_renderer.rs │ ├── plant_view.rs │ ├── sky_view.rs │ └── sun_view.rs │ ├── weather │ └── mod.rs │ ├── world │ ├── chunk_renderer.rs │ ├── chunk_view.rs │ ├── mod.rs │ ├── normal_converter.rs │ ├── tex_generator.rs │ └── world_view.rs │ └── world_manager.rs ├── plantex-server └── main.rs ├── plantex.sublime-project ├── plantex └── main.rs ├── rustfmt.toml └── server ├── Cargo.toml └── src ├── lib.rs └── server.rs /.gitignore: -------------------------------------------------------------------------------- 1 | # Generated by Cargo 2 | # will have compiled files and executables 3 | /target/ 4 | 5 | # Sublime per user temporary workspace 6 | *.sublime-workspace 7 | 8 | # Backup files created by rustfmt 9 | *.rs.bk 10 | 11 | # game config 12 | /config.toml 13 | 14 | # OSX index files 15 | .DS_Store 16 | 17 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: rust 2 | rust: stable 3 | cache: cargo 4 | sudo: false 5 | git: 6 | depth: 1 7 | before_script: 8 | - | 9 | export PATH=$HOME/.local/bin:$HOME/.cargo/bin:$PATH && 10 | ci/install-tools.sh 11 | script: 12 | - ci/run-all.sh 13 | after_success: 14 | - ci/upload-docs.sh 15 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Lukas Kalbertodt "] 3 | license = "MIT/Apache-2.0" 4 | name = "plantex" 5 | version = "0.1.0" 6 | edition = "2018" 7 | default-run = "plantex" 8 | 9 | [[bin]] 10 | name = "plantex" 11 | path = "plantex/main.rs" 12 | 13 | [[bin]] 14 | name = "plantex-server" 15 | path = "plantex-server/main.rs" 16 | 17 | [dependencies] 18 | cgmath = "0.10.0" 19 | env_logger = "0.3.4" 20 | log = "0.3.6" 21 | 22 | [dependencies.base] 23 | path = "base" 24 | 25 | [dependencies.client] 26 | path = "client" 27 | 28 | [dependencies.server] 29 | path = "server" 30 | 31 | [profile.release] 32 | codegen-units = 3 33 | -------------------------------------------------------------------------------- /LICENSE-MIT: -------------------------------------------------------------------------------- 1 | Copyright (c) 2016 Plantex Developers 2 | 3 | Permission is hereby granted, free of charge, to any 4 | person obtaining a copy of this software and associated 5 | documentation files (the "Software"), to deal in the 6 | Software without restriction, including without 7 | limitation the rights to use, copy, modify, merge, 8 | publish, distribute, sublicense, and/or sell copies of 9 | the Software, and to permit persons to whom the Software 10 | is furnished to do so, subject to the following 11 | conditions: 12 | 13 | The above copyright notice and this permission notice 14 | shall be included in all copies or substantial portions 15 | of the Software. 16 | 17 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF 18 | ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED 19 | TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A 20 | PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT 21 | SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY 22 | CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 23 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR 24 | IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER 25 | DEALINGS IN THE SOFTWARE. 26 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # :seedling: Plantex: open-world game about plants :evergreen_tree: :leaves: :herb: :palm_tree: 2 | 3 | [![Build Status](https://travis-ci.org/OsnaCS/plantex.svg?branch=master)](https://travis-ci.org/OsnaCS/plantex) 4 | [![License](https://img.shields.io/github/license/OsnaCS/plantex.svg)](http://www.apache.org/licenses/LICENSE-2.0) 5 | [![license](https://img.shields.io/github/license/mashape/apistatus.svg?maxAge=2592000)](http://opensource.org/licenses/MIT) 6 | 7 | This game was developed in a three week programming practical at the university of Osnabrück :-) 8 | 9 |

Plantex Trailer

12 | 13 | Everything you see is procedurally generated -- there are no static textures, meshes or worlds! A different seed will generate completely different textures, plants, stars and a different world. You can find more images further down. 14 | 15 | ## Run the game 16 | 17 | ### Windows binaries 18 | 19 | Precompiled binaries for Windows x64 can be downloaded on the [releases page](https://github.com/OsnaCS/plantex/releases). Latest build: [v0.1.0](https://github.com/OsnaCS/plantex/releases/download/v0.1.0/plantex-win.zip). 20 | 21 | ### Compile the game 22 | 23 | For all other platforms you have to compile the game yourself. First make sure you have a Rust compiler and `cargo` installed. Then clone this repository (or download it as ZIP file) and execute: 24 | 25 | ```bash 26 | $ cargo build --release 27 | ``` 28 | 29 | After the compilation has finished, you can run the game by either executing the binary in `./target/release/` or just `cargo run --release --bin plantex`. 30 | 31 | ### Play the game/controls 32 | 33 | You can move with `WASD` and move faster by pressing `Shift`. To look around, click inside the window to capture the mouse; afterwards you can use the mouse to rotate the camera. Click again to uncapture the mouse. 34 | 35 | When starting the game you are controlling a ghost that can fly around freely. To toggle between ghost and player press `G`. Pressing `Space` produces an upward motion (jumping when player, increasing altitude when ghost). Pressing `Ctrl` produces a downward motion. 36 | 37 | You can quickly exit the game with `ESC` and accelerate the time in the game by pressing `+`. 38 | 39 | ## Images 40 | 41 | ![next to a rain forest](http://i.imgur.com/MqHlejR.jpg) 42 | 43 | ![snow biome](http://i.imgur.com/NpCoJIg.jpg) 44 | 45 | ![different plants](http://i.imgur.com/LLGLWNy.png) 46 | 47 | ## Documentation 48 | 49 | - [**`base`**](https://osnacs.github.io/plantex/base/index.html) 50 | - [**`client`**](https://osnacs.github.io/plantex/client/index.html) 51 | - [**`server`**](https://osnacs.github.io/plantex/server/index.html) 52 | 53 | ## License 54 | 55 | Licensed under either of 56 | 57 | * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0) 58 | * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) 59 | 60 | at your option. 61 | 62 | ### Contribution 63 | 64 | Unless you explicitly state otherwise, any contribution intentionally submitted 65 | for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any 66 | additional terms or conditions. 67 | 68 | Development will probably stop after the practical has ended. If there is enough interest in the game idea, the game is probably rewritten from scratch (the code in this repository often is far from optimal). Don't hesitate to make suggestions or file PRs, though! Just keep the status of this project in mind ... 69 | -------------------------------------------------------------------------------- /base/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Lukas Kalbertodt "] 3 | license = "MIT/Apache-2.0" 4 | name = "base" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | cgmath = "0.10.0" 10 | fnv = "1.0.3" 11 | log = "0.3.6" 12 | num-traits = "0.1.33" 13 | rand = "0.3.14" 14 | noise = "0.3" 15 | -------------------------------------------------------------------------------- /base/src/gen/mod.rs: -------------------------------------------------------------------------------- 1 | //! Functionality about procedurally generating content. 2 | //! 3 | 4 | extern crate fnv; 5 | 6 | pub mod plant; 7 | pub mod world; 8 | 9 | pub use self::plant::PlantGenerator; 10 | pub use self::world::WorldGenerator; 11 | 12 | use self::fnv::FnvHasher; 13 | use rand::{SeedableRng, XorShiftRng}; 14 | use std::hash::{Hash, Hasher}; 15 | 16 | pub type Random = XorShiftRng; 17 | 18 | /// Creates a seeded RNG for use in world gen. 19 | /// 20 | /// This function takes 3 seed parameters which are hashed and mixed together. 21 | /// 22 | /// # Parameters 23 | /// 24 | /// * `world_seed`: The constant world seed as set in the config 25 | /// * `feat_seed`: Feature-specific constant seed 26 | /// * `loc_seed`: Location-seed, for example, X/Y coordinates of a feature 27 | pub fn seeded_rng(world_seed: u64, feat_seed: T, loc_seed: U) -> Random { 28 | // Hash everything, even `world_seed`, since XorShift really doesn't like seeds 29 | // with many 0s in it 30 | let mut fnv = FnvHasher::default(); 31 | world_seed.hash(&mut fnv); 32 | feat_seed.hash(&mut fnv); 33 | loc_seed.hash(&mut fnv); 34 | let rng_seed = fnv.finish(); 35 | let seed0 = (rng_seed >> 32) as u32; 36 | let seed1 = rng_seed as u32; 37 | 38 | XorShiftRng::from_seed([seed0, seed1, seed0, seed1]) 39 | } 40 | -------------------------------------------------------------------------------- /base/src/gen/plant/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod tree; 2 | 3 | use self::tree::{PlantType, TreeGen}; 4 | use prop::Plant; 5 | use rand::Rng; 6 | 7 | /// Plant generation entry point. 8 | /// 9 | /// This struct will randomly generate a plant using a more specific plant 10 | /// generator. 11 | pub enum PlantGenerator { 12 | Tree(TreeGen), 13 | } 14 | 15 | impl PlantGenerator { 16 | pub fn generate(self, rng: &mut R) -> Plant { 17 | match self { 18 | PlantGenerator::Tree(treegen) => Plant::Tree(treegen.generate(rng)), 19 | } 20 | } 21 | 22 | pub fn new(plant_type: PlantType) -> Self { 23 | PlantGenerator::Tree(TreeGen::new(plant_type)) 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /base/src/gen/world/biome.rs: -------------------------------------------------------------------------------- 1 | use gen::plant::tree::PlantType; 2 | use world::GroundMaterial; 3 | 4 | #[derive(Clone, Debug, PartialEq)] 5 | pub enum Biome { 6 | GrassLand, 7 | Desert, 8 | Snow, 9 | Forest, 10 | RainForest, 11 | Savanna, 12 | Stone, 13 | Debug, 14 | } 15 | 16 | impl Default for Biome { 17 | fn default() -> Biome { 18 | Biome::Debug 19 | } 20 | } 21 | 22 | impl Biome { 23 | pub fn material(&self) -> GroundMaterial { 24 | match *self { 25 | Biome::GrassLand => GroundMaterial::Grass, 26 | Biome::Desert => GroundMaterial::Sand, 27 | Biome::Snow => GroundMaterial::Snow, 28 | Biome::Forest => GroundMaterial::Mulch, 29 | Biome::RainForest => GroundMaterial::JungleGrass, 30 | Biome::Savanna => GroundMaterial::Dirt, 31 | Biome::Stone => GroundMaterial::Stone, 32 | Biome::Debug => GroundMaterial::Debug, 33 | } 34 | } 35 | 36 | pub fn plant_threshold(&self) -> f32 { 37 | 0.05 + match *self { 38 | Biome::GrassLand => 0.3, 39 | Biome::Desert => 0.46, 40 | Biome::Snow => 0.35, 41 | Biome::Forest => 0.25, 42 | Biome::RainForest => 0.21, 43 | Biome::Savanna => 0.375, 44 | Biome::Stone => 0.45, 45 | Biome::Debug => 1.0, 46 | } 47 | } 48 | 49 | pub fn from_climate(temperature: f32, humidity: f32) -> Biome { 50 | match (temperature, humidity) { 51 | (0.0..=0.2, 0.0..=0.2) => Biome::Stone, 52 | (0.0..=0.2, 0.2..=0.4) => Biome::Snow, 53 | (0.0..=0.2, 0.4..=1.0) => Biome::Snow, 54 | (0.2..=0.4, 0.0..=0.2) => Biome::GrassLand, 55 | (0.2..=0.4, 0.2..=0.4) => Biome::GrassLand, 56 | (0.2..=0.4, 0.4..=1.0) => Biome::Forest, 57 | (0.4..=1.0, 0.0..=0.2) => Biome::Desert, 58 | (0.4..=1.0, 0.2..=0.4) => Biome::Savanna, 59 | (0.4..=1.0, 0.4..=1.0) => Biome::RainForest, 60 | _ => Biome::Debug, 61 | } 62 | } 63 | 64 | pub fn plant_distribution(&self) -> &'static [PlantType] { 65 | match *self { 66 | Biome::GrassLand => { 67 | static PLANTS: &'static [PlantType] = &[ 68 | PlantType::WitheredTree, 69 | PlantType::OakTree, 70 | PlantType::OakTree, 71 | PlantType::ClumpOfGrass, 72 | PlantType::ClumpOfGrass, 73 | PlantType::Flower, 74 | PlantType::Flower, 75 | ]; 76 | PLANTS 77 | } 78 | Biome::Desert => { 79 | static PLANTS: &'static [PlantType] = &[PlantType::Cactus]; 80 | PLANTS 81 | } 82 | Biome::Snow => { 83 | static PLANTS: &'static [PlantType] = &[PlantType::Conifer]; 84 | PLANTS 85 | } 86 | Biome::Forest => { 87 | static PLANTS: &'static [PlantType] = &[ 88 | PlantType::WitheredTree, 89 | PlantType::Conifer, 90 | PlantType::OakTree, 91 | PlantType::OakTree, 92 | PlantType::OakTree, 93 | PlantType::Conifer, 94 | PlantType::ClumpOfGrass, 95 | PlantType::Conifer, 96 | PlantType::Flower, 97 | PlantType::Shrub, 98 | PlantType::Shrub, 99 | ]; 100 | PLANTS 101 | } 102 | Biome::RainForest => { 103 | static PLANTS: &'static [PlantType] = &[ 104 | PlantType::JungleTree, 105 | PlantType::JungleTree, 106 | PlantType::JungleTree, 107 | PlantType::JungleTree, 108 | PlantType::OakTree, 109 | PlantType::ClumpOfGrass, 110 | PlantType::ClumpOfGrass, 111 | PlantType::OakTree, 112 | PlantType::Shrub, 113 | PlantType::WitheredTree, 114 | ]; 115 | PLANTS 116 | } 117 | Biome::Savanna => { 118 | static PLANTS: &'static [PlantType] = &[ 119 | PlantType::OakTree, 120 | PlantType::ClumpOfGrass, 121 | PlantType::Shrub, 122 | PlantType::Shrub, 123 | PlantType::Shrub, 124 | PlantType::Shrub, 125 | PlantType::Shrub, 126 | PlantType::Shrub, 127 | PlantType::Shrub, 128 | PlantType::Shrub, 129 | PlantType::Shrub, 130 | PlantType::Shrub, 131 | ]; 132 | PLANTS 133 | } 134 | Biome::Stone => { 135 | static PLANTS: &'static [PlantType] = &[ 136 | PlantType::Conifer, 137 | PlantType::Conifer, 138 | PlantType::Conifer, 139 | PlantType::Conifer, 140 | PlantType::OakTree, 141 | PlantType::Conifer, 142 | PlantType::Conifer, 143 | PlantType::Conifer, 144 | ]; 145 | PLANTS 146 | } 147 | Biome::Debug => { 148 | static PLANTS: &'static [PlantType] = &[PlantType::ClumpOfGrass]; 149 | PLANTS 150 | } 151 | } 152 | } 153 | } 154 | -------------------------------------------------------------------------------- /base/src/gen/world/mod.rs: -------------------------------------------------------------------------------- 1 | //! Procedurally generating the game world. 2 | pub mod biome; 3 | 4 | use gen::plant::tree::PlantType; 5 | use gen::world::biome::Biome; 6 | use gen::{seeded_rng, PlantGenerator}; 7 | use noise::{open_simplex2, open_simplex3, PermutationTable}; 8 | use prop::plant::Plant; 9 | use rand::{Rand, Rng}; 10 | use world::{Chunk, ChunkIndex, ChunkProvider, HeightType, HexPillar}; 11 | use world::{GroundMaterial, PillarSection, Prop, CHUNK_SIZE, PILLAR_STEP_HEIGHT}; 12 | 13 | /// Land "fill noise" scaling in x, y, and z direction. 14 | const LAND_NOISE_SCALE: (f32, f32, f32) = (0.03, 0.03, 0.05); 15 | 16 | /// Main type to generate the game world. Implements the `ChunkProvider` trait 17 | /// (TODO, see #8). 18 | pub struct WorldGenerator { 19 | seed: u64, 20 | terrain_table: PermutationTable, 21 | plant_table: PermutationTable, 22 | temperature_table: PermutationTable, 23 | humidity_table: PermutationTable, 24 | } 25 | 26 | impl WorldGenerator { 27 | /// Creates the generator with the given seed. 28 | pub fn with_seed(seed: u64) -> Self { 29 | let mut terrain_rng = seeded_rng(seed, 0, ()); 30 | let mut plant_rng = seeded_rng(seed, 1, ()); 31 | let mut temperature_rng = seeded_rng(seed, 2, ()); 32 | let mut humidity_rng = seeded_rng(seed, 3, ()); 33 | 34 | WorldGenerator { 35 | seed: seed, 36 | terrain_table: PermutationTable::rand(&mut terrain_rng), 37 | plant_table: PermutationTable::rand(&mut plant_rng), 38 | temperature_table: PermutationTable::rand(&mut temperature_rng), 39 | humidity_table: PermutationTable::rand(&mut humidity_rng), 40 | } 41 | } 42 | 43 | /// Returns the seed of this world generator. 44 | pub fn seed(&self) -> u64 { 45 | self.seed 46 | } 47 | 48 | /// trying aproximate the 49 | /// steepvalues 10 20 60 200 50 | /// for temperature 0.0 0.2 0.4 1.0 51 | fn steepness_from_temperature(temperature: f32) -> f32 { 52 | 120.0 * temperature * temperature + 72.0 * temperature + 3.5 53 | } 54 | } 55 | 56 | impl ChunkProvider for WorldGenerator { 57 | fn load_chunk(&self, index: ChunkIndex) -> Option { 58 | const WORLDGEN_HEIGHT: usize = 256; 59 | // Create a 3D-Array of booleans indicating which pillar sections to fill 60 | // (Map height is unlimited in theory, but we'll limit worldgen to 64 height 61 | // units) 62 | let mut fill = [[[false; WORLDGEN_HEIGHT]; CHUNK_SIZE as usize]; CHUNK_SIZE as usize]; 63 | 64 | Some(Chunk::with_pillars(index, |pos| { 65 | let real_pos = pos.to_real(); 66 | let x = real_pos.x; 67 | let y = real_pos.y; 68 | // Pillar pos relative to first pillar 69 | let rel_pos = pos - index.0 * CHUNK_SIZE as i32; 70 | 71 | // noises 72 | let mut temperature_noise = (open_simplex2::( 73 | &self.temperature_table, 74 | &[(x as f32) * 0.0015, (y as f32) * 0.0015], 75 | ) + 0.6) 76 | / 2.0; 77 | temperature_noise += 0.035 78 | * open_simplex2::( 79 | &self.temperature_table, 80 | &[(x as f32) * 0.15, (y as f32) * 0.15], 81 | ); 82 | 83 | let mut humidity_noise = (open_simplex2::( 84 | &self.humidity_table, 85 | &[(x as f32) * 0.0015, (y as f32) * 0.0015], 86 | ) + 0.6) 87 | / 2.0; 88 | humidity_noise += 0.035 89 | * open_simplex2::( 90 | &self.humidity_table, 91 | &[(x as f32) * 0.15, (y as f32) * 0.15], 92 | ); 93 | 94 | let current_biome = Biome::from_climate(temperature_noise, humidity_noise); 95 | 96 | for i in 0..WORLDGEN_HEIGHT { 97 | if i == 0 { 98 | fill[rel_pos.q as usize][rel_pos.r as usize][i as usize] = true; 99 | continue; 100 | } 101 | 102 | let z = i as f32 * PILLAR_STEP_HEIGHT; 103 | let fill_noise = open_simplex3::( 104 | &self.terrain_table, 105 | &[ 106 | x * LAND_NOISE_SCALE.0, 107 | y * LAND_NOISE_SCALE.1, 108 | z * LAND_NOISE_SCALE.2, 109 | ], 110 | ); 111 | 112 | // The noise is (theoretically) in the range -1..1 113 | // Map the noise to a range of 0..1 114 | let fill_noise = (fill_noise + 1.0) / 2.0; 115 | 116 | // Calculate threshold to fill this "block". The lower the threshold, the more 117 | // likely this voxel is filled, so it should increase with height. 118 | let height_pct = i as f32 / WORLDGEN_HEIGHT as f32; 119 | 120 | // The threshold is calculated using a sigmoid function. These are the 121 | // parameters used: 122 | 123 | /// Minimum threshold to prevent threshold to reach 0, 124 | /// needed to have any caves at all 125 | const MIN_THRESH: f32 = 0.6; 126 | /// Threshold at half value (max. steepness, avg. terrain 127 | /// height) 128 | const THRESH_MID: f32 = 0.5; 129 | 130 | // "Steepness" of the sigmoid function. 131 | let thresh_steepness: f32 = 132 | WorldGenerator::steepness_from_temperature(temperature_noise); 133 | 134 | let sig_thresh = 135 | 1.0 / (1.0 + f32::exp(-thresh_steepness * (height_pct - THRESH_MID))); 136 | 137 | let threshold = (sig_thresh + MIN_THRESH) / (1.0 + MIN_THRESH); 138 | 139 | fill[rel_pos.q as usize][rel_pos.r as usize][i as usize] = fill_noise > threshold; 140 | } 141 | 142 | let column = &fill[rel_pos.q as usize][rel_pos.r as usize]; 143 | 144 | // Create sections for all connected `true`s in the array 145 | let mut sections = Vec::new(); 146 | let mut low = 0; 147 | let mut height = None; 148 | for i in 0..WORLDGEN_HEIGHT { 149 | let material = current_biome.material(); 150 | 151 | match (height, column[i]) { 152 | (Some(h), true) => { 153 | // Next one's still solid, increase section height 154 | height = Some(h + 1); 155 | } 156 | (Some(h), false) => { 157 | // Create a section of height `h` and start over 158 | sections.push(PillarSection::new( 159 | material, 160 | HeightType::from_units(low), 161 | HeightType::from_units(low + h), 162 | )); 163 | height = None; 164 | } 165 | (None, true) => { 166 | low = i as u16; 167 | height = Some(1); 168 | } 169 | (None, false) => {} 170 | }; 171 | } 172 | 173 | if let Some(h) = height { 174 | // Create the topmost pillar 175 | sections.push(PillarSection::new( 176 | GroundMaterial::Dirt, 177 | HeightType::from_units(low), 178 | HeightType::from_units(low + h), 179 | )); 180 | } 181 | 182 | let mut props = Vec::new(); 183 | 184 | // plants 185 | let plant_noise = 186 | open_simplex2::(&self.plant_table, &[(x as f32) * 0.25, (y as f32) * 0.25]); 187 | 188 | if plant_noise > current_biome.plant_threshold() { 189 | let mut rng = super::seeded_rng(self.seed, "TREE", (pos.q, pos.r)); 190 | 191 | let tmp = current_biome.plant_distribution(); 192 | let plant_type = rng.choose(tmp).unwrap(); 193 | 194 | let type_index = match *plant_type { 195 | PlantType::WitheredTree => 0, 196 | PlantType::Shrub => 1, 197 | PlantType::Cactus => 2, 198 | PlantType::JungleTree => 3, 199 | PlantType::ClumpOfGrass => 4, 200 | PlantType::Conifer => 5, 201 | PlantType::OakTree => 6, 202 | PlantType::Flower => 7, 203 | }; 204 | 205 | let plant_instance = rng.gen_range(0, 5); 206 | let plant_index = 8 * plant_instance + type_index; 207 | 208 | // // put the tree at the highest position 209 | let height = match sections.last() { 210 | Some(section) => section.top, 211 | None => HeightType::from_units(0), 212 | }; 213 | 214 | props.push(Prop { 215 | baseline: height, 216 | // for now, you can here set which plants should be placed 217 | // all over the world 218 | plant_index: plant_index as usize, 219 | }); 220 | } 221 | 222 | HexPillar::new( 223 | sections, 224 | props, 225 | Biome::from_climate(temperature_noise, humidity_noise), 226 | ) 227 | })) 228 | } 229 | 230 | fn get_plant_list(&self) -> Vec { 231 | let mut rng = super::seeded_rng(self.seed, "TREE", 42); 232 | 233 | let mut vec = Vec::new(); 234 | for _ in 0..5 { 235 | vec.push(PlantGenerator::new(PlantType::WitheredTree).generate(&mut rng)); 236 | vec.push(PlantGenerator::new(PlantType::Shrub).generate(&mut rng)); 237 | vec.push(PlantGenerator::new(PlantType::Cactus).generate(&mut rng)); 238 | vec.push(PlantGenerator::new(PlantType::JungleTree).generate(&mut rng)); 239 | vec.push(PlantGenerator::new(PlantType::ClumpOfGrass).generate(&mut rng)); 240 | vec.push(PlantGenerator::new(PlantType::Conifer).generate(&mut rng)); 241 | vec.push(PlantGenerator::new(PlantType::OakTree).generate(&mut rng)); 242 | vec.push(PlantGenerator::new(PlantType::Flower).generate(&mut rng)); 243 | } 244 | vec 245 | } 246 | 247 | fn is_chunk_loadable(&self, _: ChunkIndex) -> bool { 248 | // All chunks can be generated from nothing 249 | true 250 | } 251 | } 252 | -------------------------------------------------------------------------------- /base/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! Functionality used by both the plantex server and the plantex client. 2 | //! 3 | //! - the `math` module reexports everything from the `cgmath` crate and 4 | //! defines a few own type 5 | //! - the world module is all about saving and managing the game world 6 | 7 | #![allow(illegal_floating_point_literal_pattern)] 8 | 9 | pub extern crate noise; 10 | extern crate num_traits; 11 | pub extern crate rand; 12 | #[macro_use] 13 | extern crate log; 14 | 15 | pub mod gen; 16 | pub mod math; 17 | pub mod prop; 18 | pub mod world; 19 | -------------------------------------------------------------------------------- /base/src/math/axial_point.rs: -------------------------------------------------------------------------------- 1 | use super::AxialVector; 2 | use super::{AxialType, DefaultFloat, Point2f}; 3 | use math::cgmath::{Array, EuclideanSpace, MetricSpace}; 4 | use std::cmp::{max, min}; 5 | use std::fmt; 6 | use std::ops::{Add, Div, Index, IndexMut, Mul, Rem, Sub}; 7 | use world::{HEX_INNER_RADIUS, HEX_OUTER_RADIUS}; 8 | /// A 2-dimensional point in axial coordinates. See [here][hex-blog] for more 9 | /// information. 10 | /// 11 | /// [hex-blog]: http://www.redblobgames.com/grids/hexagons/#coordinates 12 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 13 | pub struct AxialPoint { 14 | pub q: AxialType, 15 | pub r: AxialType, 16 | } 17 | // TODO: implement cgmath::Array 18 | // TODO: implement cgmath::MatricSpace 19 | // TODO: implement cgmath::EuclideanSpace 20 | // TODO: implement ops::{ ... } 21 | // For all of the above, see 22 | // http://bjz.github.io/cgmath/cgmath/struct.Point2.html 23 | // 24 | impl AxialPoint { 25 | pub fn new(q: AxialType, r: AxialType) -> Self { 26 | AxialPoint { q: q, r: r } 27 | } 28 | 29 | /// Returns the position of the hexagons center in the standard coordinate 30 | /// system using `world::{HEX_INNER_RADIUS, HEX_OUTER_RADIUS}`. 31 | pub fn to_real(&self) -> Point2f { 32 | Point2f { 33 | x: ((2 * self.q - self.r) as DefaultFloat) * HEX_INNER_RADIUS, 34 | y: (self.r as DefaultFloat) * (3.0 / 2.0) * HEX_OUTER_RADIUS, 35 | } 36 | } 37 | 38 | /// Return the `AxialPoint` from a `Point2f`. It's the [pixel to hex 39 | /// algorithm from redblobgames.com][1]. 40 | /// 41 | /// [1]: http://www.redblobgames.com/grids/hexagons/#pixel-to-hex 42 | pub fn from_real(real: Point2f) -> Self { 43 | let q = (real.x * ::math::SQRT_3 / 3.0 - real.y / 3.0) / HEX_OUTER_RADIUS; 44 | let r = (real.y * 2.0 / 3.0) / HEX_OUTER_RADIUS; 45 | 46 | let s = -q - r; 47 | 48 | // Rounding 49 | let rq = q.round(); 50 | let mut rr = r.round(); 51 | let mut rs = s.round(); 52 | 53 | // To test the right rounding 54 | let q_diff = (rq - q).abs(); 55 | let r_diff = (rr - r).abs(); 56 | let s_diff = (rs - s).abs(); 57 | 58 | if q_diff > r_diff && q_diff > s_diff { 59 | // The following line is not needed since we convert from cube 60 | // coordinates to axial coordinates right away. To match the 61 | // algorithm from the blog post more closely, this line and the 62 | // if-else structure is kept. 63 | // rq = -rr - rs; 64 | } else if s_diff > r_diff { 65 | rr = -rq - rs; 66 | } else { 67 | rs = -rq - rr; 68 | } 69 | 70 | AxialPoint { 71 | q: -rs as AxialType, 72 | r: rr as AxialType, 73 | } 74 | } 75 | 76 | /// Returns the `s` component of corresponding cube coordinates. In cube 77 | /// coordinates 'q + r + s = 0', so saving `s` is redundant and can be 78 | /// calculated on the fly when needed. 79 | pub fn s(&self) -> AxialType { 80 | -self.q - self.r 81 | } 82 | } 83 | impl fmt::Debug for AxialPoint { 84 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 85 | f.debug_tuple("").field(&self.q).field(&self.r).finish() 86 | } 87 | } 88 | /// ********************Basic Arithmetics************ 89 | impl Add for AxialPoint { 90 | type Output = AxialPoint; 91 | fn add(self, rhs: AxialVector) -> AxialPoint { 92 | AxialPoint { 93 | q: self.q + rhs.q, 94 | r: self.r + rhs.r, 95 | } 96 | } 97 | } 98 | impl Sub for AxialPoint { 99 | type Output = AxialVector; 100 | fn sub(self, rhs: AxialPoint) -> AxialVector { 101 | AxialVector { 102 | q: self.q - rhs.q, 103 | r: self.r - rhs.r, 104 | } 105 | } 106 | } 107 | impl Mul for AxialPoint { 108 | type Output = AxialPoint; 109 | fn mul(self, rhs: AxialType) -> AxialPoint { 110 | AxialPoint { 111 | q: self.q * rhs, 112 | r: self.r * rhs, 113 | } 114 | } 115 | } 116 | impl Div for AxialPoint { 117 | type Output = AxialPoint; 118 | fn div(self, rhs: AxialType) -> AxialPoint { 119 | AxialPoint { 120 | q: self.q / rhs, 121 | r: self.r / rhs, 122 | } 123 | } 124 | } 125 | impl Rem for AxialPoint { 126 | type Output = AxialPoint; 127 | fn rem(self, d: AxialType) -> AxialPoint { 128 | AxialPoint { 129 | q: self.q % d, 130 | r: self.r % d, 131 | } 132 | } 133 | } 134 | /// ********************Index************ 135 | impl Index for AxialPoint { 136 | type Output = AxialType; 137 | fn index(&self, index: usize) -> &AxialType { 138 | match index { 139 | 0 => &self.q, 140 | 1 => &self.r, 141 | _ => panic!("Index out of bounds!"), 142 | } 143 | } 144 | } 145 | impl IndexMut for AxialPoint { 146 | fn index_mut(&mut self, index: usize) -> &mut AxialType { 147 | match index { 148 | 0 => &mut self.q, 149 | 1 => &mut self.r, 150 | _ => panic!("Index out of bounds!"), 151 | } 152 | } 153 | } 154 | /// ********************Array************ 155 | impl Array for AxialPoint { 156 | type Element = AxialType; 157 | fn from_value(x: AxialType) -> AxialPoint { 158 | AxialPoint { q: x, r: x } 159 | } 160 | fn sum(self) -> AxialType { 161 | self.q + self.r 162 | } 163 | fn product(self) -> AxialType { 164 | self.q * self.r 165 | } 166 | fn min(self) -> AxialType { 167 | min(self.q, self.r) 168 | } 169 | fn max(self) -> AxialType { 170 | max(self.q, self.r) 171 | } 172 | } 173 | /// ******************* Metric-Space************************ 174 | impl MetricSpace for AxialPoint { 175 | type Metric = f32; 176 | fn distance2(self, other: AxialPoint) -> Self::Metric { 177 | (((self.q - other.q) * (self.q - other.q)) + ((self.r - other.r) * (self.r - other.r))) 178 | as f32 179 | } 180 | } 181 | /// ******************* EuclideanSpace********************** 182 | impl EuclideanSpace for AxialPoint { 183 | type Scalar = i32; 184 | type Diff = AxialVector; 185 | fn origin() -> Self { 186 | AxialPoint { q: 0, r: 0 } 187 | } 188 | fn from_vec(v: Self::Diff) -> Self { 189 | AxialPoint { q: v.q, r: v.r } 190 | } 191 | fn to_vec(self) -> Self::Diff { 192 | AxialVector { 193 | q: self.q, 194 | r: self.r, 195 | } 196 | } 197 | fn dot(self, v: Self::Diff) -> Self::Scalar { 198 | (self.q * v.q) + (self.r * v.r) 199 | } 200 | } 201 | 202 | #[test] 203 | fn arithmetic_test_point() { 204 | let a: AxialPoint = AxialPoint { q: 5, r: 7 }; 205 | let b: AxialPoint = AxialPoint { q: -2, r: 0 }; 206 | let v: AxialVector = AxialVector { q: 8, r: 1 }; 207 | assert!(a.add(v) == AxialPoint { q: 13, r: 8 }); 208 | assert!(a.sub(b) == AxialVector { q: 7, r: 7 }); 209 | assert!(b.mul(4) == AxialPoint { q: -8, r: 0 }); 210 | assert!(b.div(2) == AxialPoint { q: -1, r: 0 }); 211 | assert!(a.rem(3) == AxialPoint { q: 2, r: 1 }); 212 | } 213 | #[test] 214 | fn index_test_point() { 215 | let a: AxialPoint = AxialPoint { q: 5, r: 7 }; 216 | let mut b: AxialPoint = AxialPoint { q: -2, r: 0 }; 217 | assert!(a[0] == 5); 218 | assert!(a[1] == 7); 219 | b[0] = 5; 220 | b[1] = 5; 221 | assert!(b.q == 5 && b.r == 5); 222 | } 223 | #[test] 224 | fn array_test_point() { 225 | let a = AxialPoint::from_value(7); 226 | let b: AxialPoint = AxialPoint { q: 5, r: 7 }; 227 | assert!(a.q == 7 && a.r == 7); 228 | assert!(a.sum() == 14); 229 | assert!(a.product() == 49); 230 | assert!(b.min() == 5); 231 | assert!(b.max() == 7); 232 | } 233 | #[test] 234 | fn meticspace_test_point() { 235 | let a = AxialPoint { q: 0, r: 0 }; 236 | let b = AxialPoint { q: 1, r: 1 }; 237 | assert!(a.distance2(b) == 2.0); 238 | } 239 | #[test] 240 | fn euclideanspace_test_point() { 241 | let v: AxialVector = AxialVector { q: 8, r: 1 }; 242 | let a = AxialPoint { q: 1, r: 1 }; 243 | assert!(AxialPoint::from_vec(v) == AxialPoint { q: 8, r: 1 }); 244 | assert!(a.to_vec() == AxialVector { q: 1, r: 1 }); 245 | assert!(a.dot(v) == 9); 246 | } 247 | -------------------------------------------------------------------------------- /base/src/math/axial_traits.rs: -------------------------------------------------------------------------------- 1 | use super::{Point2i, Vector2i}; 2 | 3 | /// Types that can be constructed from their axial representation. 4 | pub trait FromAxial { 5 | /// Converts from the axial type into `Self`. This is a no-op. 6 | fn from_axial(axial: T) -> Self; 7 | } 8 | 9 | impl FromAxial for Point2i { 10 | fn from_axial(axial: AxialPoint) -> Self { 11 | Point2i { 12 | x: axial.q, 13 | y: axial.r, 14 | } 15 | } 16 | } 17 | impl FromAxial for Vector2i { 18 | fn from_axial(axial: AxialVector) -> Self { 19 | Vector2i { 20 | x: axial.q, 21 | y: axial.r, 22 | } 23 | } 24 | } 25 | 26 | /// Types that can be converted into their axial representation. 27 | pub trait IntoAxial { 28 | /// Converts from `self` into the axial type. This is a no-op. 29 | fn into_axial(self) -> T; 30 | } 31 | 32 | impl IntoAxial for Point2i { 33 | fn into_axial(self) -> AxialPoint { 34 | AxialPoint { 35 | q: axial.x, 36 | r: axial.y, 37 | } 38 | } 39 | } 40 | impl IntoAxial for Vector2i { 41 | fn into_axial(self) -> AxialVector { 42 | AxialVector { 43 | q: axial.x, 44 | r: axial.y, 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /base/src/math/axial_vector.rs: -------------------------------------------------------------------------------- 1 | use super::{AxialType, DefaultFloat, Vector2f}; 2 | use math::cgmath::prelude::{Array, MetricSpace}; 3 | use math::cgmath::{VectorSpace, Zero}; 4 | use std::cmp; 5 | use std::fmt; 6 | use std::ops::{ 7 | Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Rem, RemAssign, Sub, 8 | SubAssign, 9 | }; 10 | use world::{HEX_INNER_RADIUS, HEX_OUTER_RADIUS}; 11 | 12 | /// A 2-dimensional vector in axial coordinates. See [here][hex-blog] for more 13 | /// information. 14 | /// 15 | /// [hex-blog]: http://www.redblobgames.com/grids/hexagons/#coordinates 16 | #[derive(Clone, Copy, PartialEq, Eq, Hash)] 17 | pub struct AxialVector { 18 | pub q: AxialType, 19 | pub r: AxialType, 20 | } 21 | // TODO: implement cgmath::Zero 22 | // TODO: implement cgmath::Array 23 | // TODO: implement cgmath::MatricSpace 24 | // TODO: implement cgmath::VectorSpace 25 | // TODO: implement cgmath::InnerSpace 26 | // TODO: implement ops::{ ... } 27 | // TODO: Add `unit_q()` and `unit_r()` (see `Vector2::unit_x()` for reference) 28 | // For all of the above, see 29 | // http://bjz.github.io/cgmath/cgmath/struct.Vector2.html 30 | // 31 | /// AxialVector defines a vector specifically for Axial cordinate system. 32 | impl AxialVector { 33 | pub fn new(q: AxialType, r: AxialType) -> Self { 34 | AxialVector { q: q, r: r } 35 | } 36 | /// Returns the position of the hexagons center in the standard coordinate 37 | /// system using `world::{HEX_INNER_RADIUS, HEX_OUTER_RADIUS}`. 38 | pub fn to_real(&self) -> Vector2f { 39 | Vector2f { 40 | x: ((2 * self.q - self.r) as DefaultFloat) * HEX_INNER_RADIUS, 41 | y: (self.r as DefaultFloat) * (3.0 / 2.0) * HEX_OUTER_RADIUS, 42 | } 43 | } 44 | /// Returns the `s` component of corresponding cube coordinates. In cube 45 | /// coordinates 'q + r + s = 0', so saving `s` is redundant and can be 46 | /// calculated on the fly when needed. 47 | pub fn s(&self) -> AxialType { 48 | -self.q - self.r 49 | } 50 | /// unit_q creates an default AxialVector with q:1 r:0. 51 | pub fn unit_q() -> AxialVector { 52 | AxialVector { q: 1, r: 0 } 53 | } 54 | /// unit_r creates an default AxialVector with q:0 r:1. 55 | pub fn unit_r() -> AxialVector { 56 | AxialVector { q: 0, r: 1 } 57 | } 58 | } 59 | impl fmt::Debug for AxialVector { 60 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 61 | f.debug_tuple("").field(&self.q).field(&self.r).finish() 62 | } 63 | } 64 | // ********************* Basic Arithmetic (OPS) ********************* 65 | impl Neg for AxialVector { 66 | type Output = AxialVector; 67 | fn neg(self) -> Self::Output { 68 | AxialVector { 69 | q: -self.q, 70 | r: -self.r, 71 | } 72 | } 73 | } 74 | impl Add for AxialVector { 75 | type Output = AxialVector; 76 | fn add(self, arg2: AxialVector) -> AxialVector { 77 | AxialVector { 78 | r: self.r + arg2.r, 79 | q: self.q + arg2.q, 80 | } 81 | } 82 | } 83 | impl AddAssign for AxialVector { 84 | fn add_assign(&mut self, arg2: AxialVector) { 85 | self.r += arg2.r; 86 | self.q += arg2.q; 87 | } 88 | } 89 | impl Sub for AxialVector { 90 | type Output = AxialVector; 91 | fn sub(self, arg2: AxialVector) -> AxialVector { 92 | AxialVector { 93 | r: self.r - arg2.r, 94 | q: self.q - arg2.q, 95 | } 96 | } 97 | } 98 | impl SubAssign for AxialVector { 99 | fn sub_assign(&mut self, arg2: AxialVector) { 100 | self.r -= arg2.r; 101 | self.q -= arg2.q; 102 | } 103 | } 104 | impl Mul for AxialVector { 105 | type Output = AxialVector; 106 | fn mul(self, arg2: AxialType) -> AxialVector { 107 | AxialVector { 108 | r: self.r * arg2, 109 | q: self.q * arg2, 110 | } 111 | } 112 | } 113 | impl MulAssign for AxialVector { 114 | fn mul_assign(&mut self, arg2: AxialType) { 115 | self.r *= arg2; 116 | self.q *= arg2; 117 | } 118 | } 119 | impl Div for AxialVector { 120 | type Output = AxialVector; 121 | fn div(self, arg2: AxialType) -> AxialVector { 122 | AxialVector { 123 | r: self.r / arg2, 124 | q: self.q / arg2, 125 | } 126 | } 127 | } 128 | impl DivAssign for AxialVector { 129 | fn div_assign(&mut self, arg2: AxialType) { 130 | self.r /= arg2; 131 | self.q /= arg2; 132 | } 133 | } 134 | impl Rem for AxialVector { 135 | type Output = AxialVector; 136 | fn rem(self, arg2: AxialType) -> AxialVector { 137 | AxialVector { 138 | r: self.r % arg2, 139 | q: self.q % arg2, 140 | } 141 | } 142 | } 143 | impl RemAssign for AxialVector { 144 | fn rem_assign(&mut self, arg2: AxialType) { 145 | self.r %= arg2; 146 | self.q %= arg2; 147 | } 148 | } 149 | // ************ Metric Space ************ 150 | impl MetricSpace for AxialVector { 151 | type Metric = DefaultFloat; 152 | fn distance2(self, other: AxialVector) -> DefaultFloat { 153 | ((self.q - other.q).pow(2) + (self.r - other.r).pow(2)) as DefaultFloat 154 | } 155 | } 156 | // ************ Vector Space ************ 157 | impl VectorSpace for AxialVector { 158 | type Scalar = AxialType; 159 | } 160 | // ************** Zero **************** 161 | impl Zero for AxialVector { 162 | fn zero() -> AxialVector { 163 | AxialVector { q: 0, r: 0 } 164 | } 165 | fn is_zero(&self) -> bool { 166 | self.q == 0 && self.r == 0 167 | } 168 | } 169 | // *************** Array & Index ******************* 170 | impl Array for AxialVector { 171 | type Element = AxialType; 172 | fn from_value(value: Self::Element) -> Self { 173 | AxialVector::new(value, value) 174 | } 175 | fn sum(self) -> Self::Element { 176 | self.q + self.r 177 | } 178 | fn product(self) -> Self::Element { 179 | self.q * self.r 180 | } 181 | fn min(self) -> Self::Element { 182 | cmp::min(self.q, self.r) 183 | } 184 | fn max(self) -> Self::Element { 185 | cmp::max(self.q, self.r) 186 | } 187 | } 188 | impl Index for AxialVector { 189 | type Output = AxialType; 190 | fn index(&self, index: usize) -> &Self::Output { 191 | match index { 192 | 0 => &self.q, 193 | 1 => &self.r, 194 | _ => panic!("Illegal Index Argument: was {:?}", index), 195 | } 196 | } 197 | } 198 | impl IndexMut for AxialVector { 199 | fn index_mut(&mut self, index: usize) -> &mut AxialType { 200 | match index { 201 | 0 => &mut self.q, 202 | 1 => &mut self.r, 203 | _ => panic!("Illegal Index Argument: was {:?}", index), 204 | } 205 | } 206 | } 207 | // ********************* Unit Tests ********************** 208 | #[test] 209 | fn unit_vectors_test() { 210 | // ***** Unit Vectors ***** 211 | let uvq = AxialVector::unit_q(); 212 | assert!(uvq.q == 1 && uvq.r == 0); 213 | let uvr = AxialVector::unit_r(); 214 | assert!(uvq.r == 0 && uvr.r == 1); 215 | } 216 | #[test] 217 | fn ops_test() { 218 | // ****** Arith. Operations ****** 219 | let test1 = AxialVector { q: -5, r: 2 }; 220 | let test2 = AxialVector { q: 13, r: -4 }; 221 | let uvq = AxialVector::unit_q(); 222 | let uvr = AxialVector::unit_r(); 223 | assert_eq!(-uvq, AxialVector { q: -1, r: 0 }); 224 | assert_eq!(-test1, AxialVector { q: 5, r: -2 }); 225 | assert_eq!(test1 + uvq, AxialVector { q: -4, r: 2 }); 226 | assert_eq!(test2 + test1, AxialVector { q: 8, r: -2 }); 227 | let mut test3 = AxialVector { q: -9, r: 19 }; 228 | test3 += test1; 229 | assert_eq!(test3, AxialVector { q: -14, r: 21 }); 230 | assert_eq!(test2 - test1, AxialVector { q: 18, r: -6 }); 231 | assert_eq!(test1 - uvr, AxialVector { q: -5, r: 1 }); 232 | test3 -= test2; 233 | assert!(test3.q == -27 && test3.r == 25); 234 | test3 = test1 * 2; 235 | assert!(test3.q == -10, test3.r == 4); 236 | test3 = test1 * -4; 237 | assert!(test3.q == 20 && test3.r == -8); 238 | test3 *= -3; 239 | assert!(test3.q == -60 && test3.r == 24); 240 | test3 = test3 / 3; 241 | assert!(test3.q == -20 && test3.r == 8); 242 | test3 /= 5; 243 | assert!(test3.q == -4 && test3.r == 1); 244 | test3 = test2 % 2; 245 | assert!(test3.q == 1 && test3.r == 0); 246 | test3.q = 11; 247 | test3.r = 6; 248 | test3 %= 3; 249 | assert!(test3.q == 2 && test3.r == 0); 250 | } 251 | #[test] 252 | fn metric_test() { 253 | // ********* metric space ************ 254 | let test1 = AxialVector { q: -5, r: 2 }; 255 | let test2 = AxialVector { q: 13, r: -4 }; 256 | let uvq = AxialVector::unit_q(); 257 | assert!(uvq.q == 1 && uvq.r == 0); 258 | let uvr = AxialVector::unit_r(); 259 | let mut f: f32 = 2.0; 260 | f = f.sqrt(); 261 | assert_eq!(uvq.distance(uvr), f); 262 | assert_eq!(test1.distance2(test2), 360.0); 263 | } 264 | #[test] 265 | fn zero_test() { 266 | // ********* zero ************* 267 | let test2 = AxialVector { q: 13, r: -4 }; 268 | let test3 = AxialVector::zero(); 269 | assert!(test3.q == 0 && test3.r == 0); 270 | assert!(test3.is_zero()); 271 | assert!(!(test2.is_zero())); 272 | } 273 | #[test] 274 | fn index_test() { 275 | // ********** Index ********* 276 | let mut test3 = AxialVector::zero(); 277 | test3[0] = 12; 278 | test3[1] = 4; 279 | assert!(test3.q == 12 && test3.r == 4); 280 | assert!(test3[0] == 12 && test3[1] == 4); 281 | } 282 | #[test] 283 | fn array_test() { 284 | // ********** Array *********** 285 | let test1 = AxialVector { q: -5, r: 2 }; 286 | let test2 = AxialVector { q: 13, r: -4 }; 287 | let test3 = AxialVector::from_value(3); 288 | assert!(test3.q == 3 && test3.r == 3); 289 | assert_eq!(test3.sum(), 6); 290 | assert_eq!(test3.product(), 9); 291 | assert_eq!(test1.min(), -5); 292 | assert_eq!(test2.max(), 13); 293 | } 294 | -------------------------------------------------------------------------------- /base/src/math/billboard.rs: -------------------------------------------------------------------------------- 1 | use math::Matrix4; 2 | 3 | /// Modifies a view matrix so that transformed objects always face the camera. 4 | pub fn spherical(mut matrix: Matrix4) -> Matrix4 { 5 | matrix[0][0] = 1.0; 6 | matrix[0][1] = 0.0; 7 | matrix[0][2] = 0.0; 8 | 9 | matrix[1][0] = 0.0; 10 | matrix[1][1] = 1.0; 11 | matrix[1][2] = 0.0; 12 | 13 | matrix[2][0] = 0.0; 14 | matrix[2][1] = 0.0; 15 | matrix[2][2] = 1.0; 16 | 17 | matrix 18 | } 19 | -------------------------------------------------------------------------------- /base/src/math/dimension.rs: -------------------------------------------------------------------------------- 1 | use super::{BaseNum, PartialOrd}; 2 | use num_traits::NumCast; 3 | use std::ops::Mul; 4 | 5 | /// A two-dimensional dimension. 6 | #[derive(Debug, Clone, Copy, PartialEq, Eq)] 7 | pub struct Dimension2 { 8 | pub width: T, 9 | pub height: T, 10 | } 11 | 12 | pub type Dimension2f = Dimension2; 13 | pub type Dimension2i = Dimension2; 14 | pub type Dimension2u = Dimension2; 15 | 16 | impl Dimension2 { 17 | pub fn new(width: T, height: T) -> Self { 18 | Dimension2 { 19 | width: width, 20 | height: height, 21 | } 22 | } 23 | /// Returns the area of a `Dimension` 24 | pub fn area(&self) -> ::Output { 25 | self.width * self.height 26 | } 27 | /// Scales the `Dimension2` with a scalar 28 | pub fn scale(&self, scale: T) -> Dimension2 { 29 | Dimension2 { 30 | width: self.width * scale, 31 | height: self.height * scale, 32 | } 33 | } 34 | /// Calculates the aspect ratio of a `Dimension` 35 | pub fn aspect_ratio(&self) -> f32 { 36 | assert!(!self.height.is_zero()); 37 | self.width.to_f32().unwrap() / self.height.to_f32().unwrap() 38 | } 39 | /// Shrinks a `Dimension` until it fits into another `Dimension` 40 | pub fn fitting(&self, other: Dimension2) -> Dimension2 { 41 | let scale = PartialOrd::partial_min(other.width / self.width, other.height / self.height); 42 | self.scale(scale) 43 | } 44 | /// Expands a `Dimension` until it fills another `Dimension` 45 | pub fn filling(&self, other: Dimension2) -> Dimension2 { 46 | let scale = PartialOrd::partial_max(other.width / self.width, other.height / self.height); 47 | self.scale(scale) 48 | } 49 | } 50 | 51 | #[test] 52 | fn test_area() { 53 | let test1 = Dimension2::new(3, 5); 54 | let test2 = Dimension2::new(0, 0); 55 | 56 | assert_eq!(test2.area(), 0); 57 | assert_eq!(test1.area(), 15); 58 | } 59 | #[test] 60 | fn test_scale() { 61 | let test1 = Dimension2::new(3, 5); 62 | let test2 = Dimension2::new(12, 20); 63 | 64 | assert_eq!(test1.scale(4).width, 12); 65 | assert_eq!(test2.scale(0).width, 0); 66 | } 67 | #[test] 68 | fn test_aspect_ratio() { 69 | let test1 = Dimension2::new(3, 5); 70 | 71 | assert_eq!(test1.aspect_ratio(), 3.0 / 5.0); 72 | } 73 | #[test] 74 | fn test_fitting() { 75 | let test1 = Dimension2::new(2.0, 1.0); 76 | let test2 = Dimension2::new(4.0, 3.0); 77 | 78 | assert_eq!(test2.fitting(test1), Dimension2::new(4.0 / 3.0, 1.0)); 79 | } 80 | #[test] 81 | fn test_filling() { 82 | let test1 = Dimension2::new(2.0, 1.0); 83 | let test2 = Dimension2::new(4.0, 3.0); 84 | 85 | assert_eq!(test1.filling(test2), Dimension2::new(6.0, 3.0)); 86 | } 87 | -------------------------------------------------------------------------------- /base/src/math/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module reexports everything from the `cgmath` crate and defines a few 2 | //! own types, such as `AxialVector` and `AxialPoint`. 3 | //! 4 | 5 | extern crate cgmath; 6 | 7 | mod axial_point; 8 | mod axial_vector; 9 | pub mod billboard; 10 | mod dimension; 11 | mod random; 12 | 13 | pub use self::axial_point::*; 14 | pub use self::axial_vector::*; 15 | pub use self::cgmath::*; 16 | pub use self::dimension::*; 17 | pub use self::random::*; 18 | 19 | pub type DefaultFloat = f32; 20 | pub type DefaultInt = i32; 21 | pub type DefaultUnsigned = u32; 22 | pub type AxialType = DefaultInt; 23 | 24 | pub type Vector2f = Vector2; 25 | pub type Vector2i = Vector2; 26 | pub type Vector2u = Vector2; 27 | 28 | pub type Vector3f = Vector3; 29 | pub type Vector3i = Vector3; 30 | pub type Vector3u = Vector3; 31 | 32 | pub type Vector4f = Vector4; 33 | pub type Vector4i = Vector4; 34 | pub type Vector4u = Vector4; 35 | 36 | pub type Point2f = Point2; 37 | pub type Point2i = Point2; 38 | pub type Point2u = Point2; 39 | 40 | pub type Point3f = Point3; 41 | pub type Point3i = Point3; 42 | pub type Point3u = Point3; 43 | 44 | // A bunch of compile time constants 45 | pub const SQRT_3: f32 = 1.73205080757; 46 | -------------------------------------------------------------------------------- /base/src/math/random.rs: -------------------------------------------------------------------------------- 1 | //! Utilities for random generation 2 | 3 | use math::*; 4 | 5 | use rand::{Rand, Rng}; 6 | 7 | /// Generates a random `Vector3f` that has a specific angle to `v`. 8 | /// 9 | /// The resulting vector will have a random rotation around `v`. 10 | pub fn random_vec_with_angle(rng: &mut R, v: Vector3f, angle: f32) -> Vector3f { 11 | // AWESOME hack: Generate *any* vector that is not parallel to `v`, 12 | // calculate the cross product between it and `v`. 13 | // This gets us a vector perpendicular to `v`, which we can rotate 14 | // `v` around to tilt it by `angle` degrees. 15 | let mut rand_vec = Vector3f::rand(rng); 16 | while rand_vec.angle(v).approx_eq(&Rad::new(0.0)) { 17 | // They're parallel, so generate a new vector. This is probably unnecessary, 18 | // but someone who *actually* knows their math instead of faking it should 19 | // check. 20 | rand_vec = Vector3f::rand(rng); 21 | } 22 | 23 | // Create vector around we'll tilt `v` 24 | let rot_vec = v.cross(rand_vec); 25 | 26 | // Tilt `v` by `angle` to get some vector with the right angle 27 | let tilt_rotation = Basis3::from_axis_angle(rot_vec, Deg::new(angle).into()); 28 | 29 | // Then rotate this vector randomly (0-360°) around `v` 30 | let spin_angle = rng.gen_range(0.0, 360.0); 31 | let around_we_go = Basis3::from_axis_angle(v, Deg::new(spin_angle).into()); 32 | 33 | around_we_go.rotate_vector(tilt_rotation.rotate_vector(v)) 34 | } 35 | -------------------------------------------------------------------------------- /base/src/prop/mod.rs: -------------------------------------------------------------------------------- 1 | //! Contains types of the available props. 2 | //! 3 | 4 | pub mod plant; 5 | 6 | pub use self::plant::Plant; 7 | -------------------------------------------------------------------------------- /base/src/prop/plant.rs: -------------------------------------------------------------------------------- 1 | use math::{Point3f, Vector3f}; 2 | 3 | /// Contains all types of plants we can generate. 4 | #[derive(Clone, Debug)] 5 | pub enum Plant { 6 | /// A parameterized tree-like structure. 7 | Tree(Tree), 8 | } 9 | 10 | #[derive(Clone, Debug)] 11 | pub struct Tree { 12 | /// The list of branches representing this tree. 13 | pub branches: Vec, 14 | /// Color for all trunks defined by is_trunk in `Branch` 15 | /// The vector holds elements in range `0..1` representing the RGB 16 | /// color channels. 17 | pub trunk_color: Vector3f, 18 | /// Color for all remaining `Branch`es 19 | /// The vector holds elements in range `0..1` representing the RGB 20 | /// color channels. 21 | pub leaf_color: Vector3f, 22 | } 23 | 24 | #[derive(Clone, Debug)] 25 | pub struct Branch { 26 | /// At least 2 points describing the branch. 27 | /// 28 | /// The branch extends through all `ControlPoint`s in order. 29 | pub points: Vec, 30 | 31 | /// Tells whether trunk_color or leaf_color should be used for this branch. 32 | pub is_trunk: bool, 33 | } 34 | 35 | #[derive(Clone, Copy, Debug)] 36 | pub struct ControlPoint { 37 | /// The location of this point in model coordinates (relative to the tree 38 | /// position). 39 | pub point: Point3f, 40 | /// The diameter of the branch at this point. 41 | pub diameter: f32, 42 | } 43 | -------------------------------------------------------------------------------- /base/src/world/chunk.rs: -------------------------------------------------------------------------------- 1 | use super::{ChunkIndex, HexPillar, PillarIndexComponent, CHUNK_SIZE}; 2 | use math::*; 3 | use std::iter::Iterator; 4 | use std::ops; 5 | 6 | /// Represents one part of the game world. 7 | /// 8 | /// A chunk saves `CHUNK_SIZE`² many hex pillars which are arranged rougly in 9 | /// the form of a parallelogram. See [this blog post][1] for more information 10 | /// (the shape is called "rhombus" there). 11 | /// 12 | /// This type implements the `Index` trait and can be indexed with an 13 | /// `AxialPoint`. 14 | /// 15 | /// [1]: http://www.redblobgames.com/grids/hexagons/#map-storage 16 | #[derive(Debug)] 17 | pub struct Chunk { 18 | /// All pillars are layed out in this one dimensional vector which saves 19 | /// all rows (same r-value) consecutive. 20 | pub pillars: Vec, 21 | } 22 | 23 | pub struct ChunkPillars<'a> { 24 | pub pillars: &'a [HexPillar], 25 | pub i: u16, 26 | } 27 | 28 | impl<'a> Iterator for ChunkPillars<'a> { 29 | type Item = (AxialVector, &'a HexPillar); 30 | 31 | fn next(&mut self) -> Option { 32 | if self.i < CHUNK_SIZE * CHUNK_SIZE { 33 | let axial = 34 | AxialVector::new((self.i % CHUNK_SIZE).into(), (self.i / CHUNK_SIZE).into()); 35 | let item = (axial, &self.pillars[self.i as usize]); 36 | self.i += 1; 37 | Some(item) 38 | } else { 39 | None 40 | } 41 | } 42 | } 43 | 44 | impl Chunk { 45 | /// Creates a chunk from a `Vec` 46 | pub fn from_pillars(pillars: Vec) -> Self { 47 | assert_eq!(pillars.len() as usize, CHUNK_SIZE.pow(2) as usize); 48 | 49 | Chunk { pillars: pillars } 50 | } 51 | 52 | pub fn pillars(&self) -> ChunkPillars { 53 | ChunkPillars { 54 | pillars: &self.pillars, 55 | i: 0, 56 | } 57 | } 58 | 59 | /// Safer method to get through a chunk with an ìndex 60 | pub fn get(&self, pos: AxialPoint) -> Option<&HexPillar> { 61 | let chunk_size: PillarIndexComponent = CHUNK_SIZE.into(); 62 | if pos.q >= 0 && pos.q < chunk_size && pos.r >= 0 && pos.r < chunk_size { 63 | Some(&self.pillars[(pos.r as usize) * (CHUNK_SIZE as usize) + (pos.q as usize)]) 64 | } else { 65 | None 66 | } 67 | } 68 | 69 | /// Safer method to get through a chunk with an ìndex 70 | pub fn get_mut(&mut self, pos: AxialPoint) -> Option<&mut HexPillar> { 71 | let chunk_size: PillarIndexComponent = CHUNK_SIZE.into(); 72 | if pos.q >= 0 && pos.q < chunk_size && pos.r >= 0 && pos.r < chunk_size { 73 | Some(&mut self.pillars[(pos.r as usize) * (CHUNK_SIZE as usize) + (pos.q as usize)]) 74 | } else { 75 | None 76 | } 77 | } 78 | 79 | /// Calls the given closure with all pillar positions 80 | /// that are contained in a `Chunk` 81 | pub fn for_pillars_positions(mut func: F) 82 | where 83 | F: FnMut(AxialPoint), 84 | { 85 | for q in 0..CHUNK_SIZE { 86 | for r in 0..CHUNK_SIZE { 87 | let pos = AxialPoint::new(q.into(), r.into()); 88 | func(pos); 89 | } 90 | } 91 | } 92 | 93 | /// Creates a `Chunk` using individual pillars returned by a closure 94 | pub fn with_pillars(chunk_index: ChunkIndex, mut func: F) -> Chunk 95 | where 96 | F: FnMut(AxialPoint) -> HexPillar, 97 | { 98 | let mut hec = Vec::new(); 99 | let start_q = CHUNK_SIZE as i32 * chunk_index.0.q; 100 | let start_r = CHUNK_SIZE as i32 * chunk_index.0.r; 101 | 102 | for r in start_r..start_r + CHUNK_SIZE as i32 { 103 | for q in start_q..start_q + CHUNK_SIZE as i32 { 104 | let pos = AxialPoint::new(q, r); 105 | hec.push(func(pos)); 106 | } 107 | } 108 | Chunk { pillars: hec } 109 | } 110 | } 111 | 112 | impl ops::Index for Chunk { 113 | type Output = HexPillar; 114 | 115 | fn index(&self, pos: AxialPoint) -> &Self::Output { 116 | self.get(pos).unwrap_or_else(|| { 117 | panic!( 118 | "Index out of Bounds length is: {} index was {:?}", 119 | self.pillars.len(), 120 | pos 121 | ) 122 | }) 123 | } 124 | } 125 | 126 | impl ops::IndexMut for Chunk { 127 | fn index_mut(&mut self, pos: AxialPoint) -> &mut Self::Output { 128 | self.get_mut(pos).unwrap() 129 | } 130 | } 131 | -------------------------------------------------------------------------------- /base/src/world/ground.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Copy, Debug)] 2 | pub enum GroundMaterial { 3 | Dirt, 4 | Grass, 5 | Stone, 6 | Sand, 7 | Snow, 8 | JungleGrass, 9 | Mulch, 10 | Debug, 11 | } 12 | 13 | impl GroundMaterial { 14 | // Returns color of Texture in RGB 15 | pub fn get_color(&self) -> [f32; 3] { 16 | match *self { 17 | GroundMaterial::Dirt => [0.395, 0.26, 0.13], 18 | GroundMaterial::Grass => [0.0, 0.5, 0.0], 19 | GroundMaterial::Stone => [0.5, 0.5, 0.5], 20 | GroundMaterial::Snow => [0.95, 0.95, 1.0], 21 | GroundMaterial::Sand => [0.945, 0.86, 0.49], 22 | GroundMaterial::JungleGrass => [0.1, 0.26, 0.04], 23 | GroundMaterial::Mulch => [0.332, 0.219, 0.109], 24 | GroundMaterial::Debug => [1.0, 0.0, 0.0], 25 | } 26 | } 27 | 28 | pub fn get_id(&self) -> i32 { 29 | match *self { 30 | GroundMaterial::Grass => 1, 31 | GroundMaterial::Sand => 2, 32 | GroundMaterial::Snow => 3, 33 | GroundMaterial::Dirt => 4, 34 | GroundMaterial::Stone => 5, 35 | GroundMaterial::JungleGrass => 1, 36 | GroundMaterial::Mulch => 7, 37 | GroundMaterial::Debug => 8, 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /base/src/world/hex_pillar.rs: -------------------------------------------------------------------------------- 1 | use super::{GroundMaterial, HeightType}; 2 | use gen::world::biome::Biome; 3 | 4 | /// Represents one pillar of hexgonal shape in the game world. 5 | /// 6 | /// A pillar consists of multiple sections (each of which has a material) and 7 | /// optionally props (plants, objects, ...). 8 | #[derive(Clone, Default, Debug)] 9 | pub struct HexPillar { 10 | sections: Vec, 11 | props: Vec, 12 | biome: Biome, 13 | } 14 | 15 | impl HexPillar { 16 | pub fn new(sections: Vec, props: Vec, biome: Biome) -> Self { 17 | HexPillar { 18 | sections: sections, 19 | props: props, 20 | biome: biome, 21 | } 22 | } 23 | 24 | /// Returns a slice of this pillar's sections. 25 | pub fn sections(&self) -> &[PillarSection] { 26 | &self.sections 27 | } 28 | 29 | /// Returns a slice of this pillar's sections. 30 | pub fn sections_mut(&mut self) -> &mut Vec { 31 | &mut self.sections 32 | } 33 | 34 | /// Returns a slice of this pillar's props. 35 | pub fn props(&self) -> &[Prop] { 36 | &self.props 37 | } 38 | 39 | pub fn biome(&self) -> &Biome { 40 | &self.biome 41 | } 42 | } 43 | 44 | /// Represents one section of a hex pillar. 45 | #[derive(Clone, Debug)] 46 | pub struct PillarSection { 47 | pub ground: GroundMaterial, 48 | pub bottom: HeightType, 49 | pub top: HeightType, 50 | } 51 | 52 | impl PillarSection { 53 | /// Creates a new pillar section and asserts `bottom < top`. 54 | pub fn new(ground: GroundMaterial, bottom: HeightType, top: HeightType) -> Self { 55 | assert!(bottom < top, "attempt to create an invalid pillar section"); 56 | 57 | PillarSection { 58 | ground: ground, 59 | bottom: bottom, 60 | top: top, 61 | } 62 | } 63 | } 64 | 65 | /// A prop in a hex pillar 66 | #[derive(Clone, Debug)] 67 | pub struct Prop { 68 | /// The height/baseline at which the prop starts 69 | pub baseline: HeightType, 70 | /// index in the plant_list vector 71 | pub plant_index: usize, 72 | } 73 | -------------------------------------------------------------------------------- /base/src/world/mod.rs: -------------------------------------------------------------------------------- 1 | //! Types and constants to represent a game world. 2 | //! 3 | use math; 4 | use std::fmt; 5 | 6 | pub mod chunk; 7 | pub mod ground; 8 | mod hex_pillar; 9 | mod provider; 10 | mod world; 11 | 12 | pub use self::chunk::Chunk; 13 | pub use self::ground::*; 14 | pub use self::hex_pillar::*; 15 | pub use self::provider::*; 16 | pub use self::world::World; 17 | 18 | /// Outer radius of the hexagons (from center to corner) 19 | pub const HEX_OUTER_RADIUS: f32 = 1.0; 20 | 21 | /// Inner radius of the hexagons 22 | pub const HEX_INNER_RADIUS: f32 = HEX_OUTER_RADIUS * (::math::SQRT_3 / 2.0); 23 | 24 | /// The height of the hex pillars is discretized. So instead of saving a `f32` 25 | /// to represent the height, we have fixed steps of heights and we will just 26 | /// save a `u16` to represent the height. 27 | pub const PILLAR_STEP_HEIGHT: f32 = 0.5; 28 | 29 | /// How many hex pillars a chunk is long. So the number of hex pillars in a 30 | /// chunk is `CHUNK_SIZE`². 31 | pub const CHUNK_SIZE: u16 = 16; 32 | 33 | /// This type is used to index into one dimension of the world. Thus we can 34 | /// "only" index `(PillarIndexComponent::max_value() - 35 | /// PillarIndexComponent::min_value())`² many hex pillars. 36 | pub type PillarIndexComponent = math::AxialType; 37 | 38 | /// A new-type to index Pillars. Always represents a specific Pillar in 39 | /// absolute world coordinates. 40 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 41 | pub struct PillarIndex(pub math::AxialPoint); 42 | 43 | /// A new-type to index chunks. This is different from the `PillarIndex` type 44 | /// which always represents a pillar position. So two different `PillarIndex`es 45 | /// could refer to two pillars in the same chunk, while two different 46 | /// `ChunkIndex`es always refer to two different chunks. 47 | #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] 48 | pub struct ChunkIndex(pub math::AxialPoint); 49 | 50 | /// Represents a discretized height. 51 | #[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] 52 | pub struct HeightType(pub u16); 53 | 54 | impl HeightType { 55 | /// Creates a `HeightType` with a given discrete height. 56 | pub fn from_units(n: u16) -> Self { 57 | HeightType(n) 58 | } 59 | 60 | /// Gets the number of discrete units. 61 | pub fn units(&self) -> u16 { 62 | self.0 63 | } 64 | 65 | /// Calculates the real (world position) height from the underlying 66 | /// representation. 67 | pub fn to_real(&self) -> f32 { 68 | (self.0 as f32) * PILLAR_STEP_HEIGHT 69 | } 70 | 71 | pub fn from_real(number: f32) -> f32 { 72 | number / PILLAR_STEP_HEIGHT 73 | } 74 | } 75 | 76 | impl fmt::Debug for HeightType { 77 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 78 | write!(f, "[{} -> {}]", self.0, self.to_real()) 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /base/src/world/provider.rs: -------------------------------------------------------------------------------- 1 | use super::{Chunk, ChunkIndex}; 2 | use prop::plant::Plant; 3 | 4 | /// A type that can load a game world, specifically single chunks of it. This 5 | /// could mean loading a saved world from a file, generating a world 6 | /// procedurally or loading a world from a server. 7 | pub trait ChunkProvider: Send { 8 | /// Attempt to load a chunk from the world. This may fail (e.g. when 9 | /// loading from a file and the chunk is not yet saved in the file). 10 | /// 11 | /// The given position is not a world position, but a chunk index. See the 12 | /// documentation of `ChunkIndex` for more information. 13 | /// 14 | /// # Warning 15 | /// 16 | /// This function may take some time to complete! To check whether a chunk 17 | /// can be loaded at all, prefer `is_chunk_loadable()`. 18 | fn load_chunk(&self, pos: ChunkIndex) -> Option; 19 | 20 | /// Determines whether or not the chunk at the given position can be 21 | /// loaded. This function is expected to return quickly. 22 | fn is_chunk_loadable(&self, pos: ChunkIndex) -> bool; 23 | 24 | /// Returns a Vector of all specific plants to be rendered in the world. 25 | fn get_plant_list(&self) -> Vec; 26 | } 27 | 28 | /// A dummy provider that always fails to provide a chunk. 29 | #[derive(Clone, Copy, Debug)] 30 | pub struct NullProvider; 31 | 32 | impl ChunkProvider for NullProvider { 33 | fn load_chunk(&self, _: ChunkIndex) -> Option { 34 | None 35 | } 36 | 37 | fn is_chunk_loadable(&self, _: ChunkIndex) -> bool { 38 | false 39 | } 40 | 41 | fn get_plant_list(&self) -> Vec { 42 | Vec::new() 43 | } 44 | } 45 | 46 | /// A fallback provider that holds two chunk providers with one being primary 47 | /// and one fallback. If the chunk load from the primary fails the fallback 48 | /// is being called. 49 | #[derive(Clone, Debug, Copy)] 50 | pub struct FallbackProvider { 51 | primary: P, 52 | fallback: F, 53 | } 54 | 55 | impl ChunkProvider for FallbackProvider { 56 | fn load_chunk(&self, pos: ChunkIndex) -> Option { 57 | if self.primary.is_chunk_loadable(pos) { 58 | self.primary.load_chunk(pos) 59 | } else { 60 | self.fallback.load_chunk(pos) 61 | } 62 | } 63 | 64 | fn is_chunk_loadable(&self, pos: ChunkIndex) -> bool { 65 | self.primary.is_chunk_loadable(pos) || self.fallback.is_chunk_loadable(pos) 66 | } 67 | 68 | fn get_plant_list(&self) -> Vec { 69 | self.primary.get_plant_list() 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /base/src/world/world.rs: -------------------------------------------------------------------------------- 1 | use super::{Chunk, ChunkIndex, HexPillar, PillarIndex}; 2 | use math::*; 3 | use std::collections::HashMap; 4 | 5 | /// Represents a whole game world consisting of multiple `Chunk`s. 6 | /// 7 | /// Chunks are parallelograms (roughly) that are placed next to each other 8 | /// in the world. 9 | pub struct World { 10 | // TODO: make it private after we can access it immutable via a method! (see #7) 11 | pub chunks: HashMap, 12 | } 13 | 14 | impl World { 15 | /// Creates an empty world without any chunks. 16 | pub fn empty() -> Self { 17 | World { 18 | chunks: HashMap::new(), 19 | } 20 | } 21 | 22 | /// Inserts the given chunk into the world and replaces the chunk that 23 | /// might have been at the given position before. 24 | pub fn replace_chunk(&mut self, index: ChunkIndex, chunk: Chunk) { 25 | // TODO: we might want to return the replaced chunk... 26 | self.chunks.insert(index, chunk); 27 | debug!("inserted chunk at position {:?}", index); 28 | } 29 | 30 | /// Inserts the given chunk at the given position, if there wasn't a chunk 31 | /// at that position before. In the latter case the given chunk is returned. 32 | pub fn add_chunk(&mut self, index: ChunkIndex, chunk: Chunk) -> Result<(), Chunk> { 33 | if self.chunks.contains_key(&index) { 34 | Err(chunk) 35 | } else { 36 | self.replace_chunk(index, chunk); 37 | Ok(()) 38 | } 39 | } 40 | 41 | /// Returns the hex pillar at the given world position, iff the 42 | /// corresponding chunk is loaded. 43 | pub fn pillar_at(&self, pos: PillarIndex) -> Option<&HexPillar> { 44 | let chunk_size = super::CHUNK_SIZE as i32; 45 | let mut new_pos = pos; 46 | // TODO: use `/` operator once it's implemented 47 | // let chunk_pos = pos / (super::CHUNK_SIZE as i32); 48 | if new_pos.0.q < 0 { 49 | new_pos.0.q -= 15; 50 | } 51 | if new_pos.0.r < 0 { 52 | new_pos.0.r -= 15; 53 | } 54 | let chunk_pos = new_pos.0 / chunk_size; 55 | let out = self.chunks.get(&ChunkIndex(chunk_pos)).map(|chunk| { 56 | let mut inner_pos = pos.0 % chunk_size; 57 | if inner_pos.q < 0 { 58 | inner_pos.q += chunk_size; 59 | } 60 | if inner_pos.r < 0 { 61 | inner_pos.r += chunk_size; 62 | } 63 | &chunk[inner_pos] 64 | }); 65 | 66 | if out.is_none() { 67 | debug!( 68 | "chunk {:?} is not loaded (position request {:?})", 69 | chunk_pos, pos 70 | ); 71 | } 72 | out 73 | } 74 | 75 | /// Returns the hex pillar at the given world position, iff the 76 | /// corresponding chunk is loaded. 77 | pub fn pillar_at_mut(&mut self, pos: PillarIndex) -> Option<&mut HexPillar> { 78 | let chunk_size = super::CHUNK_SIZE as i32; 79 | let mut new_pos = pos; 80 | // TODO: use `/` operator once it's implemented 81 | // let chunk_pos = pos / (super::CHUNK_SIZE as i32); 82 | if new_pos.0.q < 0 { 83 | new_pos.0.q -= 15; 84 | } 85 | if new_pos.0.r < 0 { 86 | new_pos.0.r -= 15; 87 | } 88 | let chunk_pos = new_pos.0 / chunk_size; 89 | let out = self.chunks.get_mut(&ChunkIndex(chunk_pos)).map(|chunk| { 90 | let mut inner_pos = pos.0 % chunk_size; 91 | if inner_pos.q < 0 { 92 | inner_pos.q += chunk_size; 93 | } 94 | if inner_pos.r < 0 { 95 | inner_pos.r += chunk_size; 96 | } 97 | if chunk_pos.q != chunk_pos.r { 98 | ::std::mem::swap(&mut inner_pos.r, &mut inner_pos.q); 99 | } 100 | &mut chunk[inner_pos] 101 | }); 102 | 103 | if out.is_none() { 104 | debug!( 105 | "chunk {:?} is not loaded (position request {:?})", 106 | chunk_pos, pos 107 | ); 108 | } 109 | out 110 | } 111 | 112 | /// Returns the chunk in which the given pillar exists. 113 | pub fn chunk_from_pillar(&self, pos: PillarIndex) -> Option<&Chunk> { 114 | let tmp = AxialPoint::new( 115 | pos.0.q / (super::CHUNK_SIZE as i32), 116 | pos.0.r / (super::CHUNK_SIZE as i32), 117 | ); 118 | let chunk_pos = ChunkIndex(tmp); 119 | self.chunk_at(chunk_pos) 120 | } 121 | 122 | /// Returns the requested chunk. 123 | pub fn chunk_at(&self, pos: ChunkIndex) -> Option<&Chunk> { 124 | let out = self.chunks.get(&pos); 125 | 126 | if out.is_none() { 127 | debug!("chunk {:?} is not loaded", pos); 128 | } 129 | 130 | out 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /ci/check-basic-style.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Checks 4 | # - trailing whitespaces (not allowed) 5 | # - single trailing newline (required) 6 | # - bad windows/mac line endings 7 | # - tab characters 8 | # - lines longer than XX chars 9 | 10 | # config 11 | COLS=100 12 | FOLDER="base client plantex plantex-server server" 13 | FILES='.+\.\(rs\|vert\|tesc\|tese\|geom\|frag\|comp\)' 14 | 15 | 16 | # Exit script on the first error 17 | set -o errexit -o nounset 18 | 19 | ERROR=0 20 | ### Trailing Whitespaces =========================== 21 | echo "" 22 | echo "=== Searching for lines with trailing whitespace... ===================" 23 | FOUNDTW=0 24 | for f in $(find $FOLDER -regex $FILES); do 25 | if egrep -q " +$" $f ; then 26 | echo "! Has trailing whitespace: $f" 27 | FOUNDTW=1 28 | fi 29 | done 30 | 31 | if [ $FOUNDTW -eq 0 ] ; then 32 | echo "=== None found! :-)" 33 | else 34 | echo "" 35 | echo "!!! Some lines were found. Please remove the trailing whitespace!" 36 | ERROR=1 37 | fi 38 | 39 | ### Trailing newlines =============================== 40 | echo "" 41 | echo "=== Searching for files without trailing newline... ===================" 42 | FOUND=0 43 | for f in $(find $FOLDER -regex $FILES); do 44 | lastline=$(tail -n 1 $f; echo x) 45 | lastline=${lastline%x} 46 | if [ "${lastline: -1}" != $'\n' ] ; then 47 | echo "! Has no single trailing newline: $f" 48 | FOUND=1 49 | fi 50 | done 51 | 52 | if [ $FOUND -eq 0 ] ; then 53 | echo "=== None found! :-)" 54 | else 55 | echo "" 56 | echo "!!! Some files were found. Please add a single trailing newline!" 57 | ERROR=1 58 | fi 59 | 60 | ### windows and mac OS line endings ======================= 61 | echo "" 62 | echo "=== Searching for files with wrong line endings ===================" 63 | 64 | FOUNDLE=0 65 | for f in $(find $FOLDER -regex $FILES); do 66 | if grep -q $'\r' $f ; then 67 | echo "! Has windows/mac line ending: $f" 68 | FOUNDLE=1 69 | fi 70 | done 71 | 72 | if [ $FOUNDLE -eq 0 ] ; then 73 | echo "=== None found! :-)" 74 | else 75 | echo "" 76 | echo "!!! Some lines were found. Please use unix line endings!" 77 | ERROR=1 78 | fi 79 | 80 | ## windows and mac OS line endings ======================= 81 | echo "" 82 | echo "=== Searching for files with tab chars ===================" 83 | 84 | FOUNDTAB=0 85 | for f in $(find $FOLDER -regex $FILES); do 86 | if grep -q $'\t' $f ; then 87 | echo "! Has tab character: $f" 88 | FOUNDTAB=1 89 | fi 90 | done 91 | 92 | if [ $FOUNDTAB -eq 0 ] ; then 93 | echo "=== None found! :-)" 94 | else 95 | echo "" 96 | echo "!!! Some files were found. Please indent with spaces only!" 97 | ERROR=1 98 | fi 99 | 100 | 101 | 102 | ### char limit =================================== 103 | echo "" 104 | echo "=== Searching for files with too long lines... ========================" 105 | FOUND=0 106 | for f in $(find $FOLDER -regex $FILES); do 107 | if [ $(wc -L $f | cut -d" " -f1) -gt $COLS ] ; then 108 | echo "! Line with more than $COLS chars in $f" 109 | FOUND=1 110 | fi 111 | done 112 | 113 | if [ $FOUND -eq 0 ] ; then 114 | echo "=== None found! :-)" 115 | else 116 | echo "" 117 | echo "!!! Some files were found. Please shorten those lines!" 118 | ERROR=1 119 | fi 120 | 121 | test $ERROR == 0 122 | -------------------------------------------------------------------------------- /ci/check-rustfmt.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit script on the first error 4 | set -o errexit -o nounset 5 | 6 | echo "" 7 | echo "=== Checking Rust style with rustfmt... ==============" 8 | 9 | FOLDER="base client plantex plantex-server server" 10 | FILES='.+\.rs' 11 | 12 | ERROR=0 13 | for f in $(find $FOLDER -regex $FILES); do 14 | # jesus I'm sorry, but I couldn't find a better way to use rustfmt :/ 15 | if [ $(rustfmt --write-mode=checkstyle --skip-children $f | grep 'error' | wc -l) != 0 ]; then 16 | echo "! incorrectly formatted: $f" 17 | ERROR=1 18 | fi 19 | done 20 | 21 | test $ERROR == 0 22 | -------------------------------------------------------------------------------- /ci/install-tools.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit script on the first error 4 | set -o errexit -o nounset 5 | 6 | # $PATH must be set correctly before invoking this 7 | 8 | if ! type ghp-import > /dev/null; then 9 | echo "" 10 | echo "=== Installing ghp-import ================" 11 | pip install ghp-import --user 12 | fi 13 | 14 | if ! type rustfmt > /dev/null; then 15 | echo "" 16 | echo "=== Installing rustfmt ===============" 17 | cargo install rustfmt 18 | fi 19 | 20 | type ghp-import > /dev/null 21 | type rustfmt > /dev/null 22 | -------------------------------------------------------------------------------- /ci/run-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit script on the first error 4 | set -o errexit -o nounset 5 | 6 | MY_PATH="`dirname \"$0\"`" 7 | 8 | # basic style check 9 | $MY_PATH/check-basic-style.sh 10 | 11 | # check that everything is formatted with rustfmt 12 | # EDIT: we don't want to check it anymore... 13 | # $MY_PATH/check-rustfmt.sh 14 | 15 | # check that everything compiles and all tests pass 16 | $MY_PATH/test-all.sh 17 | 18 | echo "++++++++++++++++++++++++++++++++++++++++++++++++++++" 19 | echo "+ Everything is fine! +" 20 | echo "++++++++++++++++++++++++++++++++++++++++++++++++++++" 21 | -------------------------------------------------------------------------------- /ci/test-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # This file needs to be run from the git root directory! 4 | 5 | # Exit script on the first error 6 | set -o errexit -o nounset 7 | 8 | # Build the main crate which depends on all others 9 | echo "" 10 | echo "=== Building Plantex ===============" 11 | cargo build 12 | 13 | for crate in base client server plantex; do 14 | echo "" 15 | echo "=== Testing $crate... ==============" 16 | cargo test -p $crate 17 | done 18 | -------------------------------------------------------------------------------- /ci/upload-docs.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # Exit script on the first error 4 | set -e 5 | 6 | echo "" 7 | echo "=== Generating documentation =================" 8 | 9 | if [ "$TRAVIS_BRANCH" == "master" ] && [ "$TRAVIS_PULL_REQUEST" == "false" ]; then 10 | cargo doc 11 | echo "" 12 | echo "=== Uploading docs ===============" 13 | ghp-import -n target/doc 14 | git push -qf https://${GH_TOKEN}@github.com/${TRAVIS_REPO_SLUG}.git gh-pages 15 | fi 16 | -------------------------------------------------------------------------------- /client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Lukas Kalbertodt "] 3 | license = "MIT/Apache-2.0" 4 | name = "client" 5 | publish = false 6 | version = "0.1.0" 7 | 8 | [dependencies] 9 | log = "0.3.6" 10 | clap = "2" 11 | toml = "0.1" 12 | regex = "0.1" 13 | rand = "0.3" 14 | glium = "0.23" 15 | noise = "0.3" 16 | 17 | [dependencies.base] 18 | path = "../base" 19 | -------------------------------------------------------------------------------- /client/shader/adaption_shrink.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // =================================================================== 4 | // Brightness Adaption Shrinking 5 | // =================================================================== 6 | // 7 | // 8 | 9 | out float FragColor; 10 | 11 | in VertexData { 12 | vec2 frag_texcoord; 13 | } i; 14 | 15 | uniform sampler2D image; 16 | 17 | void main() 18 | { 19 | FragColor = texture(image, i.frag_texcoord).r; 20 | } 21 | -------------------------------------------------------------------------------- /client/shader/adaption_shrink.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/advancedtonemapping.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D decal_texture; 4 | uniform float exposure; 5 | 6 | in VertexData { 7 | vec2 frag_texcoord; 8 | } i; 9 | 10 | layout(location = 0) out vec4 frag_output; 11 | 12 | const float shoulder_strength = 0.15; 13 | const float linear_strength = 0.50; 14 | const float linear_angle = 0.10; 15 | const float toe_strength = 0.20; 16 | const float toe_numerator = 0.02; 17 | const float toe_denominator = 0.30; 18 | const float linear_white = 11.2; 19 | 20 | // A gamma value of 2.2 is a default gamma value that 21 | // roughly estimates the average gamma of most displays. 22 | // sRGB color space 23 | const float gamma = 2.2; 24 | 25 | //other set of values that seem to look good 26 | /* 27 | const float shoulder_strength = 0.22; 28 | const float linear_strength = 0.30; 29 | const float linear_angle = 0.10; 30 | const float toe_strength = 0.20; 31 | const float toe_numerator = 0.01; 32 | const float toe_denominator = 0.30; 33 | const float linear_white = 11.2; // is the smallest luminance that will be mapped to 1.0 34 | */ 35 | 36 | // filmic tonemapping ala uncharted 2 37 | // note if you use it you have to redo all your lighting 38 | // you can not just switch it back on if your lighting is for 39 | // no dynamic range. 40 | vec3 tonemap(vec3 x) 41 | { 42 | return ( 43 | (x*(shoulder_strength*x+linear_angle*linear_strength)+toe_strength*toe_numerator)/ 44 | (x*(shoulder_strength*x+linear_strength)+toe_strength*toe_denominator) 45 | ) 46 | -toe_numerator/toe_denominator; 47 | } 48 | 49 | void main() { 50 | vec3 color = texture(decal_texture, i.frag_texcoord).rgb; 51 | //add Exposure 52 | color *= exposure; 53 | //exposure BIAS 54 | color *= 2.0; 55 | 56 | color = tonemap(color); 57 | vec3 whitecsale = 1.0/tonemap(vec3(linear_white)); 58 | color *= whitecsale; 59 | //gamma 60 | color = pow(color, vec3(1/gamma)); 61 | frag_output = vec4(color, 1.0); 62 | } 63 | -------------------------------------------------------------------------------- /client/shader/advancedtonemapping.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/blend.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D image; 4 | 5 | in VertexData { 6 | vec2 frag_texcoord; 7 | } i; 8 | 9 | layout(location = 0) out vec4 out_color; 10 | 11 | void main() 12 | { 13 | out_color = texture(image, i.frag_texcoord); 14 | } 15 | -------------------------------------------------------------------------------- /client/shader/blend.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/bloom_blending.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D world_tex; 4 | uniform sampler2D bloom_tex; 5 | 6 | in VertexData { 7 | vec2 frag_texcoord; 8 | } i; 9 | 10 | layout(location = 0) out vec4 out_color; 11 | 12 | void main() 13 | { 14 | vec3 hdr_color = texture(world_tex, i.frag_texcoord).rgb; 15 | vec3 bloom_color = texture(bloom_tex, i.frag_texcoord).rgb; 16 | bloom_color = exp(log(bloom_color) * 0.5); 17 | out_color = vec4(hdr_color + 0.5 * bloom_color, 1.0); 18 | // out_color = vec4(hdr_color, 1.0); 19 | } 20 | -------------------------------------------------------------------------------- /client/shader/bloom_blending.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/bloom_blur.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // =================================================================== 4 | // Simple Gaussian Blur 5 | // =================================================================== 6 | // 7 | // Blurs the filtered Light Map. Use Gaussian Blur affecting the current 8 | // and the 4 in one direction neighboring fragments (along a horizontal or 9 | // vertical line). 10 | 11 | out vec4 FragColor; 12 | 13 | in VertexData { 14 | vec2 frag_texcoord; 15 | } i; 16 | 17 | uniform sampler2D image; 18 | 19 | uniform bool horizontal; // indicates whether to blur horizontal or vertical 20 | 21 | // weights of the gauss curve, weight[0] corresponds to center fragment 22 | //uniform float weight[5] = float[] (0.227027, 0.1945946, 0.1216216, 0.054054, 0.016216); 23 | 24 | uniform float weight = 0.227027; 25 | uniform float d12 = 1.38461536; 26 | uniform float d34 = 3.23076704; 27 | uniform float k12 = 0.3162162; 28 | uniform float k34 = 0.07027; 29 | 30 | void main() 31 | { 32 | vec2 TexCoords = i.frag_texcoord; 33 | vec2 tex_offset = 1.0 / textureSize(image, 0); // gets size of single texel 34 | // current fragment's contribution: 35 | vec3 result = texture(image, TexCoords).rgb * weight; 36 | if(horizontal) 37 | { 38 | //first fragment pair 39 | result += texture(image, TexCoords + vec2(tex_offset.x * d12, 0.0)).rgb * k12; 40 | result += texture(image, TexCoords - vec2(tex_offset.x * d12, 0.0)).rgb * k12; 41 | 42 | //second fragment pair 43 | result += texture(image, TexCoords + vec2(tex_offset.x * d34, 0.0)).rgb * k34; 44 | result += texture(image, TexCoords - vec2(tex_offset.x * d34, 0.0)).rgb * k34; 45 | } 46 | else 47 | { 48 | //first fragment pair 49 | result += texture(image, TexCoords + vec2(0.0, tex_offset.x * d12)).rgb * k12; 50 | result += texture(image, TexCoords - vec2(0.0, tex_offset.x * d12)).rgb * k12; 51 | 52 | //second fragment pair 53 | result += texture(image, TexCoords + vec2(0.0, tex_offset.x * d34)).rgb * k34; 54 | result += texture(image, TexCoords - vec2(0.0, tex_offset.x * d34)).rgb * k34; 55 | } 56 | FragColor = vec4(result, 1.0); 57 | } 58 | -------------------------------------------------------------------------------- /client/shader/bloom_blur.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/bloom_filter.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | // =================================================================== 4 | // Bloom Filter Map 5 | // =================================================================== 6 | // 7 | // Filter bright areas and only map those to the texture. 8 | 9 | uniform sampler2D decal_texture; 10 | uniform float bloom_threshhold; 11 | 12 | in VertexData { 13 | vec2 frag_texcoord; 14 | } i; 15 | 16 | layout(location = 0) out vec4 out_color; 17 | 18 | void main() { 19 | vec3 col = texture(decal_texture, i.frag_texcoord).rgb; 20 | 21 | 22 | // transform proper brightness. Values adapt for eye vision, for explanation see: 23 | // https://en.wikipedia.org/wiki/Luma_%28video%29#Use_of_relative_luminance 24 | float lum = dot(col, vec3(0.2126, 0.7152, 0.0722)); 25 | float percentage = (lum - bloom_threshhold) / (3 * bloom_threshhold); 26 | out_color = vec4(mix(vec3(0), col, clamp(percentage, 0, 1)), 0); 27 | // if (lum > bloom_threshhold) { 28 | // out_color = vec4(col, 1.0); 29 | // } else { 30 | // out_color = vec4(0); 31 | // } 32 | } 33 | -------------------------------------------------------------------------------- /client/shader/bloom_filter.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/chunk_shadow.frag: -------------------------------------------------------------------------------- 1 | // When rendering the shadow map, we aren't interested in color information, so 2 | // this can stay empty (depth will be written automatically). 3 | // FIXME All shadow map fragment shaders are the same, so we should stop 4 | // duplicating the files 5 | 6 | #version 140 7 | 8 | out vec2 out_color; 9 | 10 | void main() { 11 | float depth = gl_FragCoord.z; 12 | 13 | out_color = vec2(depth, depth * depth); 14 | } 15 | -------------------------------------------------------------------------------- /client/shader/chunk_shadow.vert: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | // Per-vertex attributes 4 | in vec3 position; 5 | in vec3 normal; 6 | 7 | uniform mat4 proj_matrix; 8 | uniform mat4 view_matrix; 9 | uniform vec2 offset; 10 | 11 | void main() { 12 | vec4 world_coords = vec4( 13 | position.xy + offset.xy, 14 | position.z, 15 | 1); 16 | gl_Position = proj_matrix * view_matrix * world_coords; 17 | } 18 | -------------------------------------------------------------------------------- /client/shader/chunk_std.frag: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | in vec4 shadowCoord; 4 | in vec3 x_material_color; 5 | in vec3 surfaceNormal; 6 | in vec3 x_position; 7 | in float x_radius; 8 | in vec2 x_tex_coords; 9 | flat in int x_ground; 10 | in vec3 pos; 11 | 12 | out vec3 color; 13 | 14 | // Vector from the camera to the sun 15 | uniform vec3 sun_dir; 16 | uniform sampler2D shadow_map; 17 | uniform vec3 sun_color; 18 | uniform vec3 sky_light; 19 | uniform vec3 cam_pos; 20 | 21 | // Normals to bump mapping the textures 22 | uniform sampler2D normal_sand; 23 | uniform sampler2D normal_snow; 24 | uniform sampler2D normal_grass; 25 | uniform sampler2D normal_stone; 26 | uniform sampler2D normal_dirt; 27 | uniform sampler2D normal_mulch; 28 | 29 | 30 | // Surface textures 31 | uniform sampler2D sand_texture; 32 | uniform sampler2D grass_texture; 33 | uniform sampler2D snow_texture; 34 | uniform sampler2D stone_texture; 35 | uniform sampler2D dirt_texture; 36 | uniform sampler2D mulch_texture; 37 | 38 | const float SHADOW_BIAS = 0.001; // FIXME does this even work? 39 | const float AMBIENT = 0.2; 40 | 41 | 42 | float lightCoverage(vec2 moments, float fragDepth) { 43 | float E_x2 = moments.y; 44 | float Ex_2 = moments.x * moments.x; 45 | float variance = E_x2 - Ex_2; 46 | float mD = moments.x - fragDepth; 47 | float mD_2 = mD * mD; 48 | float p = variance / (variance + mD_2); 49 | return min(max(p, fragDepth <= moments.x ? 1.0 : 0.0), 1.0); 50 | } 51 | 52 | /// Calculates Tangent Binormal Normal (tbn) Matrix 53 | mat3 cotangent_frame(vec3 normal, vec3 pos, vec2 uv) { 54 | vec3 dp1 = dFdx(pos); 55 | vec3 dp2 = dFdy(pos); 56 | vec2 duv1 = dFdx(uv); 57 | vec2 duv2 = dFdy(uv); 58 | 59 | vec3 dp2perp = cross(dp2, normal); 60 | vec3 dp1perp = cross(normal, dp1); 61 | vec3 T = dp2perp * duv1.x + dp1perp * duv2.x; 62 | vec3 B = dp2perp * duv1.y + dp1perp * duv2.y; 63 | 64 | float invmax = inversesqrt(max(dot(T, T), dot(B, B))); 65 | return mat3(T * invmax, B * invmax, normal); 66 | } 67 | 68 | void main() { 69 | // vec3 lightCoords = shadowCoord.xyz; 70 | vec3 lightCoords = shadowCoord.xyz / shadowCoord.w; 71 | lightCoords = lightCoords * 0.5 + 0.5; 72 | float lit; 73 | if (lightCoords.x < 0 || lightCoords.x > 1 || lightCoords.y < 0 || lightCoords.y > 1) { 74 | // Outside of shadow map. Guess brightness from sun angle. 75 | float sunDot = dot(vec3(0, 0, 1), normalize(sun_dir)); 76 | lit = clamp(-sunDot * 3.0, 0, 1); 77 | } else { 78 | vec2 moments = texture(shadow_map, lightCoords.xy).xy; 79 | lit = lightCoverage(moments, lightCoords.z - SHADOW_BIAS); 80 | lit = mix(1.0, lit, 0.9); 81 | // lit = 1; 82 | } 83 | if (sun_dir.z > 0) { 84 | // lit *= max(0, 1 + dot(vec3(0, 0, 1), normalize(sun_dir)) * 2); 85 | lit = 0; 86 | } 87 | 88 | // ================== 89 | // LIGHT CALCULATIONS 90 | // ================== 91 | 92 | // Calculate normal map relative to surface 93 | vec3 normal_map; 94 | // Correcting the height to fit the height to the texture coordinates 95 | vec2 tex = vec2(x_tex_coords.x, fract(x_tex_coords.y)); 96 | 97 | // Determine which surface texture to use 98 | vec3 diffuse_color; 99 | 100 | if (x_ground == 1) { 101 | normal_map = texture(normal_grass, tex).rgb; 102 | diffuse_color = texture(grass_texture, x_tex_coords).rgb; 103 | } else if (x_ground == 2) { 104 | normal_map = texture(normal_sand, tex).rgb; 105 | diffuse_color = texture(sand_texture, x_tex_coords).rgb; 106 | } else if (x_ground == 3) { 107 | normal_map = texture(normal_snow, tex).rgb; 108 | diffuse_color = texture(snow_texture, x_tex_coords).rgb; 109 | } else if (x_ground == 4) { 110 | normal_map = texture(normal_dirt, tex).rgb; 111 | diffuse_color = texture(dirt_texture, x_tex_coords).rgb; 112 | } else if (x_ground == 5) { 113 | normal_map = texture(normal_stone, tex).rgb; 114 | diffuse_color = texture(stone_texture, x_tex_coords).rgb; 115 | } else if (x_ground == 7) { 116 | normal_map = texture(normal_mulch, tex).rgb; 117 | diffuse_color = texture(mulch_texture, x_tex_coords).rgb; 118 | } 119 | 120 | // Calculate Tangent Binormal Normal (tbn) Matrix to multiply with normal_map 121 | // to convert to real normals 122 | mat3 tbn = cotangent_frame(normal_map, x_position, x_tex_coords); 123 | vec3 real_normal = normalize(tbn * -(normal_map * 1.6 - 1.0)); 124 | 125 | // Calculate diffuse light component 126 | float diffuse = max(0.0, dot(-normalize(sun_dir), real_normal)); 127 | 128 | 129 | diffuse_color *= x_material_color; 130 | 131 | // DEBUG: for showing normal map as texture 132 | // vec3 normal_color_map = texture(normal_sand, x_tex_coords).rgb; 133 | 134 | vec3 specular_color = vec3(1.0, 1.0, 1.0); 135 | vec3 half_direction = sun_dir; 136 | float specular = pow(max(dot(half_direction, real_normal), 0.0), 16.0); 137 | 138 | // if (x_ground == 1) { 139 | // diffuse *= 40; 140 | // specular *= 40; 141 | // } else if (x_ground == 2) { 142 | // diffuse *= 35; 143 | // specular *= 35; 144 | // } else if (x_ground == 3) { 145 | // diffuse *= 10; 146 | // specular *= 10; 147 | // } else if (x_ground == 4) { 148 | // diffuse *= 35; 149 | // specular *= 35; 150 | // } else if (x_ground == 5) { 151 | // diffuse *= 20; 152 | // specular *= 20; 153 | // } else if (x_ground == 7) { 154 | // diffuse *= 30; 155 | // specular *= 30; 156 | // } 157 | 158 | // Final color calculation 159 | // color = diffuse_color * AMBIENT + diffuse_color * diffuse * lit + diffuse_color * specular; 160 | color = diffuse_color * sky_light + lit * sun_color * diffuse_color * (diffuse + specular); 161 | 162 | // Set Border to distinguish hexagons 163 | if (x_radius > 0.98) { 164 | // color *= 0.7; 165 | color = log(1 + color); 166 | } 167 | 168 | // apply fog to final color 169 | vec3 player_to_frag = x_position - cam_pos; 170 | float distance = pow(length(player_to_frag.xy) / 130, 2); 171 | if (distance > 1) { 172 | distance = 1; 173 | } 174 | float fog_time= -(sun_dir.z / 3) * 30; 175 | 176 | 177 | if (fog_time < 0) { 178 | fog_time = 0; 179 | } 180 | 181 | vec3 fog_color = vec3(0.05 + fog_time, 0.05 + fog_time, 0.1 + fog_time); 182 | vec3 tmp_color = mix(color, fog_color, distance/1.5); 183 | color = tmp_color; 184 | // color = tmp_color * ; 185 | // color += tmp_color * sky_light; 186 | } 187 | -------------------------------------------------------------------------------- /client/shader/chunk_std.vert: -------------------------------------------------------------------------------- 1 | #version 140 2 | 3 | // Per-vertex attributes 4 | in vec3 position; 5 | in vec3 normal; 6 | in float radius; 7 | in vec2 tex_coords; 8 | in vec3 material_color; 9 | in int ground; 10 | 11 | // ----------------------- 12 | 13 | out vec3 x_position; 14 | out vec3 surfaceNormal; 15 | out float x_radius; 16 | out vec2 x_tex_coords; 17 | out vec3 x_material_color; 18 | flat out int x_ground; 19 | 20 | // Vertex/Pixel coordinates in shadow map 21 | out vec4 shadowCoord; 22 | out vec3 pos; 23 | 24 | // ----------------------- 25 | 26 | uniform mat4 proj_matrix; 27 | uniform mat4 view_matrix; 28 | uniform mat4 depth_view_proj; 29 | uniform vec2 offset; 30 | 31 | void main() { 32 | vec4 world_coords = vec4( 33 | position.xy + offset.xy, 34 | position.z, 35 | 1); 36 | 37 | gl_Position = proj_matrix * view_matrix * world_coords; 38 | shadowCoord = depth_view_proj * world_coords; 39 | 40 | surfaceNormal = normal; 41 | x_material_color = material_color; 42 | x_radius = radius; 43 | x_tex_coords = tex_coords; 44 | 45 | x_position = world_coords.xyz; 46 | x_ground = ground; 47 | } 48 | -------------------------------------------------------------------------------- /client/shader/outline.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | out vec4 color; 4 | 5 | void main() { 6 | color = vec4(1.0, 0.0, 0.0, 0.3); 7 | } 8 | -------------------------------------------------------------------------------- /client/shader/outline.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec3 position; 4 | in vec3 normal; 5 | 6 | uniform vec3 outline_pos; 7 | uniform mat4 proj_matrix; 8 | uniform mat4 view_matrix; 9 | 10 | void main() { 11 | gl_Position = proj_matrix * view_matrix * vec4(position.x + outline_pos.x, 12 | position. y + outline_pos.y ,position.z + outline_pos.z, 1); 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/plant_shadow.frag: -------------------------------------------------------------------------------- 1 | // When rendering the shadow map, we aren't interested in color information, so 2 | // this can stay empty (depth will be written automatically). 3 | // FIXME All shadow map fragment shaders are the same, so we should stop 4 | // duplicating the files 5 | 6 | #version 140 7 | 8 | out vec2 out_color; 9 | 10 | void main() { 11 | float depth = gl_FragCoord.z; 12 | 13 | out_color = vec2(depth, depth * depth); 14 | } 15 | -------------------------------------------------------------------------------- /client/shader/plant_shadow.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec3 position; 4 | in vec3 color; 5 | in vec3 normal; 6 | in vec3 offset; 7 | 8 | out vec3 vPosition; 9 | out vec3 material_color; 10 | out vec3 surfaceNormal; 11 | out vec3 vOffset; 12 | 13 | 14 | uniform mat4 proj_matrix; 15 | uniform mat4 view_matrix; 16 | 17 | void main() { 18 | gl_Position = proj_matrix * view_matrix * vec4(position + offset, 1); 19 | } 20 | -------------------------------------------------------------------------------- /client/shader/plants.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec4 shadowCoord; 4 | in vec3 material_color; 5 | in vec3 tes_normal; 6 | in vec3 tes_color; 7 | in vec3 pos; 8 | 9 | out vec3 color; 10 | 11 | // Vector from the camera to the sun 12 | uniform vec3 sun_dir; 13 | uniform sampler2D shadow_map; 14 | uniform vec3 sun_color; 15 | uniform vec3 sky_light; 16 | 17 | const float SHADOW_BIAS = 0.001; // FIXME does this even work? 18 | const float AMBIENT = 0.1; 19 | 20 | 21 | float lightCoverage(vec2 moments, float fragDepth) { 22 | float E_x2 = moments.y; 23 | float Ex_2 = moments.x * moments.x; 24 | float variance = E_x2 - Ex_2; 25 | float mD = moments.x - fragDepth; 26 | float mD_2 = mD * mD; 27 | float p = variance / (variance + mD_2); 28 | return min(max(p, fragDepth <= moments.x ? 1.0 : 0.0), 1.0); 29 | } 30 | 31 | void main() { 32 | vec3 lightCoords = shadowCoord.xyz / shadowCoord.w; 33 | // vec3 lightCoords = shadowCoord.xyz; 34 | lightCoords = lightCoords * 0.5 + 0.5; 35 | 36 | float sunDot = dot(vec3(0, 0, 1), normalize(sun_dir)); 37 | // sunDot = 1; 38 | float lit; 39 | if (lightCoords.x < 0 || lightCoords.x > 1 || lightCoords.y < 0 || lightCoords.y > 1) { 40 | // Outside of shadow map. Guess brightness from sun angle. 41 | lit = clamp(-sunDot * 3.0, 0, 1); 42 | } else { 43 | vec2 moments = texture(shadow_map, lightCoords.xy).xy; 44 | lit = lightCoverage(moments, lightCoords.z - SHADOW_BIAS); 45 | // if (sun_dir.z > 0) { 46 | // lit = 0; 47 | // } 48 | // lit = 1; 49 | } 50 | 51 | float diffuse = max(0.0, dot(-normalize(sun_dir), normalize(tes_normal))); 52 | 53 | vec3 tmp_color = tes_color * sky_light + tes_color * diffuse * lit * sun_color; 54 | // vec3 tmp_color = diffuse * sun_color; 55 | // vec3 tmp_color = tes_color * AMBIENT + tes_color * diffuse * lit; 56 | 57 | // apply fog to final color 58 | float distance = (length(pos) / 130) * (length(pos) / 130); 59 | if (distance > 1) { 60 | distance = 1; 61 | } 62 | float fog_time = -(sun_dir.z / 3) * 30; 63 | 64 | if (fog_time < 0) { 65 | fog_time = 0; 66 | } 67 | 68 | vec3 fog_color = vec3(0.05 + fog_time, 0.05 + fog_time, 0.1 + fog_time); 69 | tmp_color = mix(tmp_color, fog_color, distance/1.5); 70 | // color = tmp_color * sky_light; 71 | // color += tmp_color * sun_color; 72 | color = tmp_color; 73 | } 74 | -------------------------------------------------------------------------------- /client/shader/plants.tcs: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | layout(vertices = 1) out; 4 | 5 | in vec3 vPosition[]; 6 | in vec3 material_color[]; 7 | in vec3 surfaceNormal[]; 8 | in vec3 vOffset[]; 9 | 10 | out vec3 offset[]; 11 | 12 | uniform vec3 camera_pos; 13 | 14 | const float TESS_LEVEL_INNER = 5.0; 15 | const float TESS_LEVEL_OUTER = 5.0; 16 | 17 | struct OutputPatch { 18 | vec3 WorldPos_B030; 19 | vec3 WorldPos_B021; 20 | vec3 WorldPos_B012; 21 | vec3 WorldPos_B003; 22 | vec3 WorldPos_B102; 23 | vec3 WorldPos_B201; 24 | vec3 WorldPos_B300; 25 | vec3 WorldPos_B210; 26 | vec3 WorldPos_B120; 27 | vec3 WorldPos_B111; 28 | vec3 Normal[3]; 29 | vec3 Color; 30 | }; 31 | 32 | patch out OutputPatch oPatch; 33 | 34 | vec3 ProjectToPlane(vec3 Point, vec3 PlanePoint, vec3 PlaneNormal) 35 | { 36 | vec3 v = Point - PlanePoint; 37 | float Len = dot(v, normalize(PlaneNormal)); 38 | vec3 d = Len * normalize(PlaneNormal); 39 | vec3 res = (Point-d); 40 | vec3 add = (res - PlanePoint); 41 | return (res + 2 * add); 42 | } 43 | 44 | void CalcPositions() 45 | { 46 | // Edges are names according to the opposing vertex 47 | vec3 EdgeB300 = oPatch.WorldPos_B003 - oPatch.WorldPos_B030; 48 | vec3 EdgeB030 = oPatch.WorldPos_B300 - oPatch.WorldPos_B003; 49 | vec3 EdgeB003 = oPatch.WorldPos_B030 - oPatch.WorldPos_B300; 50 | 51 | // Generate two midpoints on each edge 52 | vec3 tmp_B021 = oPatch.WorldPos_B030 + EdgeB300 / 3.0; 53 | vec3 tmp_B012 = oPatch.WorldPos_B030 + EdgeB300 * 2.0 / 3.0; 54 | vec3 tmp_B102 = oPatch.WorldPos_B003 + EdgeB030 / 3.0; 55 | vec3 tmp_B201 = oPatch.WorldPos_B003 + EdgeB030 * 2.0 / 3.0; 56 | vec3 tmp_B210 = oPatch.WorldPos_B300 + EdgeB003 / 3.0; 57 | vec3 tmp_B120 = oPatch.WorldPos_B300 + EdgeB003 * 2.0 / 3.0; 58 | 59 | // adding height_information 60 | oPatch.WorldPos_B021 = ProjectToPlane(tmp_B021, oPatch.WorldPos_B030, 61 | oPatch.Normal[0]); 62 | 63 | oPatch.WorldPos_B012 = ProjectToPlane(tmp_B012, oPatch.WorldPos_B003, 64 | oPatch.Normal[1]); 65 | 66 | oPatch.WorldPos_B102 = ProjectToPlane(tmp_B102, oPatch.WorldPos_B003, 67 | oPatch.Normal[1]); 68 | 69 | oPatch.WorldPos_B201 = ProjectToPlane(tmp_B201, oPatch.WorldPos_B300, 70 | oPatch.Normal[2]); 71 | 72 | oPatch.WorldPos_B210 = ProjectToPlane(tmp_B210, oPatch.WorldPos_B300, 73 | oPatch.Normal[2]); 74 | 75 | oPatch.WorldPos_B120 = ProjectToPlane(tmp_B120, oPatch.WorldPos_B030, 76 | oPatch.Normal[0]); 77 | 78 | // Handle the center 79 | vec3 Center = (oPatch.WorldPos_B003 + oPatch.WorldPos_B030 + oPatch.WorldPos_B300) / 3.0; 80 | vec3 tmp = (oPatch.WorldPos_B021 + oPatch.WorldPos_B012 + oPatch.WorldPos_B102 + 81 | oPatch.WorldPos_B201 + oPatch.WorldPos_B210 + oPatch.WorldPos_B120) / 6.0; 82 | 83 | oPatch.WorldPos_B111 = tmp + (tmp - Center) / 2.0; 84 | 85 | } 86 | 87 | void main() { 88 | // setting Normals 89 | for (int i = 0; i < 3; i++) { 90 | oPatch.Normal[i] = surfaceNormal[i]; 91 | } 92 | 93 | offset[gl_InvocationID] = vOffset[gl_InvocationID]; 94 | 95 | // The original vertices stay the same 96 | oPatch.WorldPos_B030 = vPosition[0]; 97 | oPatch.WorldPos_B003 = vPosition[1]; 98 | oPatch.WorldPos_B300 = vPosition[2]; 99 | 100 | // handle tesselation level on distance 101 | int len = int(length(camera_pos - offset[0]) / 3); 102 | float inner = TESS_LEVEL_INNER; 103 | float outer = TESS_LEVEL_OUTER; 104 | 105 | if (len > TESS_LEVEL_INNER - 1 || len > TESS_LEVEL_INNER - 1){ 106 | inner = 1.0; 107 | outer = 1.0; 108 | 109 | 110 | // set to dummy values (not setting them causes glitches on some GPUs) 111 | oPatch.WorldPos_B021 = vec3(0); 112 | oPatch.WorldPos_B012 = vec3(0); 113 | oPatch.WorldPos_B102 = vec3(0); 114 | oPatch.WorldPos_B201 = vec3(0); 115 | oPatch.WorldPos_B210 = vec3(0); 116 | oPatch.WorldPos_B120 = vec3(0); 117 | oPatch.WorldPos_B111 = vec3(0); 118 | 119 | } else { 120 | inner = inner - len; 121 | outer = outer - len; 122 | CalcPositions(); 123 | } 124 | 125 | oPatch.Color = material_color[0]; 126 | 127 | gl_TessLevelInner[0] = inner; 128 | gl_TessLevelOuter[0] = outer; 129 | gl_TessLevelOuter[1] = outer; 130 | gl_TessLevelOuter[2] = outer; 131 | } 132 | -------------------------------------------------------------------------------- /client/shader/plants.tes: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | layout(triangles, equal_spacing, ccw) in; 4 | 5 | struct OutputPatch { 6 | vec3 WorldPos_B030; 7 | vec3 WorldPos_B021; 8 | vec3 WorldPos_B012; 9 | vec3 WorldPos_B003; 10 | vec3 WorldPos_B102; 11 | vec3 WorldPos_B201; 12 | vec3 WorldPos_B300; 13 | vec3 WorldPos_B210; 14 | vec3 WorldPos_B120; 15 | vec3 WorldPos_B111; 16 | vec3 Normal[3]; 17 | vec3 Color; 18 | }; 19 | 20 | patch in OutputPatch oPatch; 21 | 22 | out vec3 tePosition; 23 | out vec3 tes_normal; 24 | out vec3 tes_color; 25 | out vec3 pos; 26 | 27 | // Vertex/Pixel coordinates in shadow map 28 | out vec4 shadowCoord; 29 | 30 | in vec3 offset[]; 31 | uniform mat4 proj_matrix; 32 | uniform mat4 view_matrix; 33 | uniform vec3 camera_pos; 34 | uniform mat4 depth_view_proj; 35 | 36 | // interpolates 3D coordinates 37 | vec3 interpolate3D(vec3 v0, vec3 v1, vec3 v2) 38 | { 39 | return vec3(gl_TessCoord.x) * v0 + vec3(gl_TessCoord.y) * v1 + vec3(gl_TessCoord.z) * v2; 40 | } 41 | 42 | void main() 43 | { 44 | // Interpolate the attributes of the output vertex using the barycentric coordinates 45 | tes_color = oPatch.Color; 46 | tes_normal = interpolate3D(oPatch.Normal[0], oPatch.Normal[1], oPatch.Normal[2]); 47 | 48 | // Bezier deforming 49 | float u = gl_TessCoord.x; 50 | float v = gl_TessCoord.y; 51 | float w = gl_TessCoord.z; 52 | 53 | tePosition = oPatch.WorldPos_B300 * w * w * w + 54 | oPatch.WorldPos_B030 * u * u * u + 55 | oPatch.WorldPos_B003 * v * v * v + 56 | oPatch.WorldPos_B210 * 3.0 * w * w * u + 57 | oPatch.WorldPos_B120 * 3.0 * w * u * u + 58 | oPatch.WorldPos_B201 * 3.0 * w * w * v + 59 | oPatch.WorldPos_B021 * 3.0 * u * u * v + 60 | oPatch.WorldPos_B102 * 3.0 * w * v * v + 61 | oPatch.WorldPos_B012 * 3.0 * u * v * v + 62 | oPatch.WorldPos_B111 * 6.0 * w * u * v; 63 | 64 | // projection on camera 65 | vec3 worldPos = tePosition + offset[0]; 66 | gl_Position = proj_matrix * view_matrix * vec4(worldPos, 1); 67 | 68 | // position for fog 69 | pos = worldPos - camera_pos; 70 | pos = vec3(pos.x, pos.y, 0); 71 | 72 | // yes, this somehow is *also* the world position 73 | vec4 world = view_matrix * vec4(worldPos, 1); 74 | gl_Position = proj_matrix * world; 75 | // coordinates on the shadow map 76 | shadowCoord = depth_view_proj * vec4(worldPos, 1); 77 | } 78 | -------------------------------------------------------------------------------- /client/shader/plants.vert: -------------------------------------------------------------------------------- 1 | #version 400 2 | 3 | in vec3 position; 4 | in vec3 color; 5 | in vec3 normal; 6 | in vec3 offset; 7 | 8 | out vec3 vPosition; 9 | out vec3 material_color; 10 | out vec3 surfaceNormal; 11 | out vec3 vOffset; 12 | 13 | void main() { 14 | //setting out Variables for Tesselation Controll Shader 15 | material_color = color; 16 | surfaceNormal= normal; 17 | vPosition = position; 18 | vOffset=offset.xyz; 19 | } 20 | -------------------------------------------------------------------------------- /client/shader/plants_notess.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec4 shadowCoord; 4 | in vec3 tes_normal; 5 | in vec3 tes_color; 6 | in vec3 pos; 7 | 8 | out vec3 color; 9 | 10 | // Vector from the camera to the sun 11 | uniform vec3 sun_dir; 12 | uniform sampler2D shadow_map; 13 | uniform vec3 sun_color; 14 | uniform vec3 sky_light; 15 | 16 | const float SHADOW_BIAS = 0.001; // FIXME does this even work? 17 | const float AMBIENT = 0.1; 18 | 19 | 20 | float lightCoverage(vec2 moments, float fragDepth) { 21 | float E_x2 = moments.y; 22 | float Ex_2 = moments.x * moments.x; 23 | float variance = E_x2 - Ex_2; 24 | float mD = moments.x - fragDepth; 25 | float mD_2 = mD * mD; 26 | float p = variance / (variance + mD_2); 27 | return min(max(p, fragDepth <= moments.x ? 1.0 : 0.0), 1.0); 28 | } 29 | 30 | void main() { 31 | vec3 lightCoords = shadowCoord.xyz / shadowCoord.w; 32 | // vec3 lightCoords = shadowCoord.xyz; 33 | lightCoords = lightCoords * 0.5 + 0.5; 34 | 35 | float sunDot = dot(vec3(0, 0, 1), normalize(sun_dir)); 36 | // sunDot = 1; 37 | float lit; 38 | if (lightCoords.x < 0 || lightCoords.x > 1 || lightCoords.y < 0 || lightCoords.y > 1) { 39 | // Outside of shadow map. Guess brightness from sun angle. 40 | lit = clamp(-sunDot * 3.0, 0, 1); 41 | } else { 42 | vec2 moments = texture(shadow_map, lightCoords.xy).xy; 43 | lit = lightCoverage(moments, lightCoords.z - SHADOW_BIAS); 44 | // if (sun_dir.z > 0) { 45 | // lit = 0; 46 | // } 47 | // lit = 1; 48 | } 49 | 50 | float diffuse = max(0.0, dot(-normalize(sun_dir), normalize(tes_normal))); 51 | 52 | vec3 tmp_color = tes_color * sky_light + tes_color * diffuse * lit * sun_color; 53 | // vec3 tmp_color = diffuse * sun_color; 54 | // vec3 tmp_color = tes_color * AMBIENT + tes_color * diffuse * lit; 55 | 56 | // apply fog to final color 57 | float distance = (length(pos) / 130) * (length(pos) / 130); 58 | if (distance > 1) { 59 | distance = 1; 60 | } 61 | float fog_time = -(sun_dir.z / 3) * 30; 62 | 63 | if (fog_time < 0) { 64 | fog_time = 0; 65 | } 66 | 67 | vec3 fog_color = vec3(0.05 + fog_time, 0.05 + fog_time, 0.1 + fog_time); 68 | tmp_color = mix(tmp_color, fog_color, distance/1.5); 69 | // color = tmp_color * sky_light; 70 | // color += tmp_color * sun_color; 71 | color = tmp_color; 72 | } 73 | -------------------------------------------------------------------------------- /client/shader/plants_notess.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec3 position; 4 | in vec3 color; 5 | in vec3 normal; 6 | in vec3 offset; 7 | 8 | out vec3 pos; 9 | out vec3 tes_normal; 10 | out vec3 tes_color; 11 | 12 | // Vertex/Pixel coordinates in shadow map 13 | out vec4 shadowCoord; 14 | 15 | uniform mat4 proj_matrix; 16 | uniform mat4 view_matrix; 17 | uniform vec3 camera_pos; 18 | uniform mat4 depth_view_proj; 19 | 20 | void main() { 21 | tes_color = color; 22 | tes_normal = normal; 23 | 24 | // projection on camera 25 | vec3 worldPos = position + offset; 26 | gl_Position = proj_matrix * view_matrix * vec4(worldPos, 1); 27 | 28 | // position for fog 29 | pos = worldPos - camera_pos; 30 | pos = vec3(pos.x, pos.y, 0); 31 | 32 | // yes, this somehow is *also* the world position 33 | vec4 world = view_matrix * vec4(worldPos, 1); 34 | gl_Position = proj_matrix * world; 35 | // coordinates on the shadow map 36 | shadowCoord = depth_view_proj * vec4(worldPos, 1); 37 | } 38 | -------------------------------------------------------------------------------- /client/shader/relative_luminance.frag: -------------------------------------------------------------------------------- 1 | #version 330 core 2 | 3 | // =================================================================== 4 | // Greyscale with relative luminance 5 | // =================================================================== 6 | // 7 | // Creates a greyscale texture with relative luminance. 8 | 9 | out float FragColor; 10 | 11 | in VertexData { 12 | vec2 frag_texcoord; 13 | } i; 14 | 15 | uniform sampler2D image; 16 | 17 | void main() 18 | { 19 | float t = 10; 20 | float tt = 1/t; 21 | FragColor = log((tt + dot(texture(image, i.frag_texcoord).rgb, 22 | vec3(0.2126, 0.7152, 0.0722))) * t); 23 | } 24 | -------------------------------------------------------------------------------- /client/shader/relative_luminance.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/shadow_debug.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D decal_texture; 4 | 5 | in VertexData { 6 | vec2 frag_texcoord; 7 | } i; 8 | 9 | layout(location = 0) out vec4 frag_output; 10 | 11 | void main() { 12 | float val = texture(decal_texture, i.frag_texcoord).r; 13 | if (val == 1.0) { 14 | frag_output = vec4(1, 0, 0, 1); 15 | } else { 16 | frag_output = vec4(val); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /client/shader/shadow_debug.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/skydome.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec3 x_unit_coords; 4 | 5 | uniform vec3 u_sun_pos; 6 | uniform sampler2D u_star_map; 7 | 8 | uniform vec3 u_sun_color; 9 | uniform vec3 u_sky_light; 10 | 11 | out vec3 color; 12 | 13 | #define PI 3.141592653589793 14 | 15 | void main() { 16 | // Calculate spherical coordinates 17 | vec3 unit_vector = normalize(x_unit_coords); 18 | 19 | // Calculates theta 20 | // unit_vector.z varies from [-1..1] therefore arccos from unit_vector.z, 21 | // which is theta, varies from [PI..0] respectively 22 | float theta = acos(unit_vector.z); 23 | 24 | float z = unit_vector.z; 25 | 26 | float upperblue = 1.0; 27 | float blue = 0.729; 28 | float lightblue = 0.459; 29 | float dirtyblue = 0.366; 30 | float dirtyyellow = 0.169; 31 | float yellow = 0.056; 32 | float red = 0.0; 33 | float bottomred = -0.1; 34 | float black = -0.3; 35 | 36 | float lum_factor = 10.0; //change this into a constant 37 | 38 | vec3 upperblue_color = vec3 (12.0, 43.0, 80.0)*lum_factor; 39 | vec3 blue_color = vec3 (22.0, 77.0, 142.0)*lum_factor; 40 | vec3 lightblue_color = vec3 (62.0, 134.0, 142.0)*lum_factor; 41 | vec3 dirtyblue_color = vec3 (105.0, 142.0, 137.0)*lum_factor; 42 | vec3 dirtyyellow_color = vec3 (170.0, 142.0, 85.0)*lum_factor; 43 | vec3 yellow_color = vec3 (255.0, 125.0, 0.0)*lum_factor; 44 | vec3 red_color = vec3 (255.0, 0.0, 0.0)*lum_factor; 45 | vec3 bottomred_color = vec3 (120.0, 0.0, 0.0)*lum_factor; 46 | vec3 black_color = vec3 (0.0, 0.0, 0.0); 47 | 48 | vec3 sunset_color; 49 | 50 | if (z <= upperblue && z >= blue) { 51 | // size of section of sky between these two colors 52 | float size = upperblue - blue; 53 | // difference of position of `z` to the lower border 54 | float diff = z - blue; 55 | // percentage of shift to the upper border 56 | float percent = diff/size; 57 | // difference between the two colors will be shifted 58 | // on the lower border the difference to the upper border 59 | // (times a shift-`percent`) is added 60 | sunset_color = mix(blue_color, upperblue_color, percent); 61 | } else if (z < blue && z >= lightblue) { 62 | float size = blue - lightblue; 63 | float diff = z - lightblue; 64 | float percent = diff/size; 65 | sunset_color = mix(lightblue_color, blue_color, percent); 66 | } else if (z < lightblue && z >= dirtyblue) { 67 | float size = lightblue - dirtyblue; 68 | float diff = z - dirtyblue; 69 | float percent = diff/size; 70 | sunset_color = mix(dirtyblue_color, lightblue_color, percent); 71 | } else if ( z < dirtyblue && z >= dirtyyellow) { 72 | float size = dirtyblue - dirtyyellow; 73 | float diff = z - dirtyyellow; 74 | float percent = diff/size; 75 | sunset_color = mix(dirtyyellow_color, dirtyblue_color, percent); 76 | } else if (z < dirtyyellow && z >= yellow) { 77 | float size = dirtyyellow - yellow; 78 | float diff = z - yellow; 79 | float percent = diff/size; 80 | sunset_color = mix(yellow_color, dirtyyellow_color, percent); 81 | } else if (z < yellow && z >= red) { 82 | float size = yellow - red; 83 | float diff = z - red; 84 | float percent = diff/size; 85 | sunset_color = mix(red_color, yellow_color, percent); 86 | } else if (z < red && z >= bottomred) { 87 | float size = red - bottomred; 88 | float diff = z - bottomred; 89 | float percent = diff/size; 90 | sunset_color = mix(bottomred_color, red_color, percent); 91 | } else if (z < bottomred && z >= black) { 92 | float size = bottomred - black; 93 | float diff = z - black; 94 | float percent = diff/size; 95 | sunset_color = mix(black_color, bottomred_color, percent); 96 | sunset_color = (black_color + (bottomred_color - black_color) * percent); 97 | } else { 98 | sunset_color = black_color; 99 | } 100 | 101 | // Calculates phi 102 | // goes from 0..2*PI 103 | float phi = atan(unit_vector.y, unit_vector.x) ; 104 | 105 | // Calculate dummy blue gradient sky color 106 | vec3 high_noon_color = vec3(((theta / PI)-0.2)*0.5,((theta / PI)-0.1)*0.5,1.0)*lum_factor; 107 | sunset_color = sunset_color / 255; 108 | vec3 nightblue_color = (vec3 (0.0, 0.0, 1.0) / 255); 109 | 110 | float nighttime = -0.1; 111 | float sunrise_start = 0.0; 112 | float sunset_start = 0.1; 113 | float high_noon_start = 0.3; 114 | 115 | float sun_z = normalize(u_sun_pos).z; 116 | float sun_x = normalize(u_sun_pos).x; 117 | float sun_y = normalize(u_sun_pos).y; 118 | 119 | float sun_phi = atan(sun_y, sun_x) ; 120 | 121 | // distance between current vertex and sun in phi direction 122 | // divided by 2*PI to get a value between 0 and 1 123 | // (where 1 corresponds to 2*PI (360 degrees)) 124 | float phi_diff = abs(phi-sun_phi)/(2*PI); 125 | if (phi_diff > 0.5) { 126 | phi_diff = 1.0 - phi_diff; 127 | } 128 | 129 | phi_diff = 1 - 2 * phi_diff; 130 | phi_diff = phi_diff*phi_diff; 131 | phi_diff = 1 - phi_diff; 132 | // phi_diff *= 2.2; 133 | // if (phi_diff>1) { 134 | // phi_diff=1; 135 | // } 136 | 137 | float theta_tmp = 1 - clamp(theta, 0.0, 1.0); 138 | 139 | 140 | float sun_start; 141 | if (sun_x > 0) { 142 | sun_start = sunrise_start; 143 | } else { 144 | sun_start = sunset_start; 145 | } 146 | 147 | // sky colors corresponding to time 148 | // night 149 | if (sun_z < nighttime) { 150 | color = nightblue_color; 151 | // night to sunrise OR sunset to night 152 | } else if (sun_z >= nighttime && sun_z < sun_start) { 153 | float size = sun_start - nighttime; 154 | float diff = sun_z - nighttime; 155 | float percent= diff/size; 156 | color = mix(nightblue_color, sunset_color, percent); 157 | color = mix(color, nightblue_color, phi_diff); 158 | color = mix(color, nightblue_color, theta_tmp); 159 | // sunrise to high_noon OR high_noon to sunset 160 | } else if (sun_z >= sun_start && sun_z < high_noon_start) { 161 | float size = high_noon_start - sun_start; 162 | float diff = sun_z - sun_start; 163 | float percent= diff/size; 164 | // color = mix(sunset_color, high_noon_color, percent); 165 | color = mix(sunset_color, nightblue_color, phi_diff); 166 | color = mix(color, nightblue_color, theta_tmp); 167 | color = mix(color, high_noon_color, percent); 168 | // high_noon 169 | } else if (sun_z >= high_noon_start) { 170 | color = high_noon_color; 171 | } 172 | 173 | // add stars 174 | 175 | // later better, so the top of the sky has stars too 176 | // float star = texture(u_star_map, vec2(theta*0.8, 0.5 + theta*phi*0.8)); 177 | vec3 test = normalize(vec3(sin(theta)*cos(phi),sin(theta)*sin(phi),0)); 178 | test *= theta; 179 | float star = texture(u_star_map, vec2(test.x, test.y)).r; 180 | 181 | vec3 star_color = vec3(0.0, 0.0, 0.0); 182 | 183 | // float star_value = 1 + theta * 0.01; 184 | 185 | star_color = vec3(max(0, (star - 0.48)) * 15)*0.4; 186 | 187 | color = color * 0.1 * (u_sun_color * 0.3 + u_sky_light) + star_color; 188 | } 189 | -------------------------------------------------------------------------------- /client/shader/skydome.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec3 i_position; 4 | in vec3 i_unit_coords; 5 | 6 | uniform mat4 u_proj_matrix; 7 | uniform mat4 u_view_matrix; 8 | uniform vec3 u_sun_pos; 9 | 10 | out vec3 x_unit_coords; 11 | 12 | void main() { 13 | gl_Position = u_proj_matrix * u_view_matrix * vec4(i_position, 1); 14 | 15 | x_unit_coords = i_unit_coords; 16 | } 17 | -------------------------------------------------------------------------------- /client/shader/sun.frag: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec3 x_unit_coords; 4 | out vec4 color; 5 | uniform vec3 sun_pos; 6 | 7 | void main() { 8 | float temp = (x_unit_coords.x * x_unit_coords.x + x_unit_coords.y * x_unit_coords.y) ; 9 | if (temp <= 1.0) { 10 | color = vec4(1.2, 1.1 - (1.0- sun_pos.z)/1.45, (1.1) - (1.0 - sun_pos.z)/1.0, 1)*10.0; 11 | } else { 12 | discard; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /client/shader/sun.vert: -------------------------------------------------------------------------------- 1 | #version 150 2 | 3 | in vec3 i_position; 4 | in vec3 i_unit_coords; 5 | 6 | uniform mat4 u_proj_matrix; 7 | uniform mat4 u_view_matrix; 8 | uniform mat4 u_model; 9 | 10 | out vec3 x_unit_coords; 11 | 12 | void main() { 13 | 14 | mat4 matrix = u_view_matrix * u_model; 15 | matrix[0][0] = 1.0; 16 | matrix[0][1] = 0.0; 17 | matrix[0][2] = 0.0; 18 | 19 | matrix[1][0] = 0.0; 20 | matrix[1][1] = 1.0; 21 | matrix[1][2] = 0.0; 22 | 23 | matrix[2][0] = 0.0; 24 | matrix[2][1] = 0.0; 25 | matrix[2][2] = 1.0; 26 | 27 | gl_Position = u_proj_matrix * matrix * vec4(i_position, 1); 28 | x_unit_coords = i_unit_coords; 29 | } 30 | -------------------------------------------------------------------------------- /client/shader/tonemapping.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | uniform sampler2D decal_texture; 4 | uniform float exposure; 5 | 6 | 7 | in VertexData { 8 | vec2 frag_texcoord; 9 | } i; 10 | 11 | layout(location = 0) out vec4 out_color; 12 | 13 | void main() { 14 | // A gamma value of 2.2 is a default gamma value that 15 | // roughly estimates the average gamma of most displays. 16 | // sRGB color space 17 | const float gamma = 2.2; 18 | vec3 hdr_color = texture(decal_texture, i.frag_texcoord).rgb; 19 | // Exposure tone mapping 20 | vec3 mapped = vec3(1.0) - exp(-hdr_color * exposure); 21 | // Gamma correction 22 | mapped = pow(mapped, vec3(gamma)); 23 | 24 | out_color = vec4(mapped, 1.0); 25 | } 26 | -------------------------------------------------------------------------------- /client/shader/tonemapping.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | layout(location = 0) in vec4 in_position; 4 | layout(location = 1) in vec2 in_texcoord; 5 | 6 | out VertexData { 7 | vec2 frag_texcoord; 8 | } o; 9 | 10 | void main() { 11 | o.frag_texcoord = in_texcoord; 12 | gl_Position = in_position; 13 | } 14 | -------------------------------------------------------------------------------- /client/shader/weather.frag: -------------------------------------------------------------------------------- 1 | #version 330 2 | 3 | in vec2 out_position; 4 | in vec4 out_color; 5 | 6 | uniform int form; 7 | uniform vec3 sky_light; 8 | uniform vec3 sun_color; 9 | 10 | out vec4 color; 11 | 12 | void main() { 13 | 14 | // multiply our color with given HDR values 15 | color = vec4(out_color) * vec4(sky_light / 2, 1.0) * vec4(sun_color / 2, 1.0); 16 | 17 | if (length(out_position) > 1) { 18 | discard; 19 | } 20 | 21 | if (form == 3) { 22 | color.a = 0.7 - (length(out_position)/2.0); 23 | } 24 | 25 | if (form == 2) { 26 | color.a = 1 - (length(out_position)/1.5); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /client/shader/weather.vert: -------------------------------------------------------------------------------- 1 | #version 330 2 | in vec3 position; 3 | in vec3 point; 4 | 5 | uniform vec4 color; 6 | uniform mat4 proj_matrix; 7 | uniform mat4 view_matrix; 8 | uniform mat4 scaling_matrix; 9 | uniform int form; 10 | uniform vec3 sun_color; 11 | uniform vec3 sky_light; 12 | 13 | 14 | out vec2 out_position; 15 | out vec4 out_color; 16 | 17 | void main() { 18 | 19 | mat4 world_matrix = mat4 ( 20 | 1.0, 0.0, 0.0, 0.0, 21 | 0.0, 1.0, 0.0, 0.0, 22 | 0.0, 0.0, 1.0, 0.0, 23 | position.x, position.y, position.z, 1.0); 24 | 25 | mat4 view = view_matrix * world_matrix; 26 | 27 | if (form == 1) { 28 | view[0][0] = 1.0; 29 | view[0][1] = 0.0; 30 | view[0][2] = 0.0; 31 | 32 | view[1][0] = 0.0; 33 | view[1][1] = 1.0; 34 | view[1][2] = 0.0; 35 | } 36 | 37 | if (form == 2 || form == 3) { 38 | view[0][0] = 1.0; 39 | view[0][1] = 0.0; 40 | view[0][2] = 0.0; 41 | 42 | view[1][0] = 0.0; 43 | view[1][1] = 0.0; 44 | view[1][2] = 1.0; 45 | 46 | view[2][0] = 0.0; 47 | view[2][1] = 1.0; 48 | view[2][2] = 0.0; 49 | } 50 | 51 | 52 | gl_Position = proj_matrix * view * scaling_matrix * vec4(point.x, 0.0, point.y, 1.0); 53 | out_position = vec2(point.x, point.y); 54 | out_color = color; 55 | } 56 | -------------------------------------------------------------------------------- /client/src/camera.rs: -------------------------------------------------------------------------------- 1 | use base::math::*; 2 | use std::f32::consts; 3 | 4 | #[derive(Clone, Copy)] 5 | pub struct Camera { 6 | pub position: Point3f, // PRP 7 | // VUV will be calculated on the fly 8 | // used to calculate the look_at_point 9 | pub theta: f32, 10 | pub phi: f32, 11 | pub aspect_ratio: f32, 12 | proj: Matrix4, 13 | } 14 | 15 | impl Camera { 16 | /// Create new Camera with given aspect ratio 17 | pub fn new(aspect_ratio: f32) -> Camera { 18 | Camera { 19 | position: Point3::new(15.0, 10.0, 50.0), 20 | phi: -0.27, 21 | theta: 2.6, 22 | aspect_ratio: aspect_ratio, 23 | proj: perspective(deg(60.0), aspect_ratio, 0.1, 3_000.0), 24 | } 25 | } 26 | 27 | /// returns a camera based on a vector to look at 28 | pub fn new_from_vector(pos: Point3f, look: Vector3f, aspect_ratio: f32) -> Self { 29 | let mut look_phi = ((look.x) / ((look.x * look.x + look.y * look.y).sqrt())).acos(); 30 | if look.y < 0.0 { 31 | look_phi = 2.0 * consts::PI - look_phi; 32 | } 33 | 34 | let look_theta = 35 | (consts::PI / 2.0) - ((look.z) / ((look.x * look.x + look.y * look.y).sqrt())).atan(); 36 | 37 | Camera { 38 | position: pos, 39 | phi: look_phi, 40 | theta: look_theta, 41 | aspect_ratio: aspect_ratio, 42 | proj: perspective(deg(60.0), aspect_ratio, 0.1, 3_000.0), 43 | } 44 | } 45 | 46 | /// Returns the projection matrix 47 | pub fn proj_matrix(&self) -> Matrix4 { 48 | self.proj 49 | } 50 | 51 | pub fn set_proj_matrix(&mut self, proj: Matrix4) { 52 | self.proj = proj; 53 | } 54 | 55 | /// Returns view matrix 56 | pub fn view_matrix(&self) -> Matrix4 { 57 | Matrix4::look_at(self.position, self.get_look_at_point(), Vector3::unit_z()) 58 | } 59 | 60 | /// Calculates the look_at_point by adding the current position to the 61 | /// look_at_vector 62 | pub fn get_look_at_point(&self) -> Point3f { 63 | self.position + self.get_look_at_vector() 64 | } 65 | 66 | /// Calculates the look_at_vector by using theta and phi on the unit sphere 67 | pub fn get_look_at_vector(&self) -> Vector3f { 68 | Vector3f::new( 69 | self.theta.sin() * self.phi.cos(), 70 | self.theta.sin() * self.phi.sin(), 71 | self.theta.cos(), 72 | ) 73 | } 74 | 75 | /// Internal function to move the position of the camera 76 | 77 | /// Will be called by the other functions (move_forwars etc) 78 | pub fn move_by(&mut self, pos_diff: Vector3f) { 79 | self.position += pos_diff; 80 | } 81 | 82 | /// Method to call when **forward movement** is needed 83 | 84 | /// `factor` is a factor to scale the movement speed 85 | 86 | /// `factor` has to be positive for foward movement 87 | pub fn move_forward(&mut self, factor: f32) { 88 | let mut lookatvector = self.get_look_at_vector(); 89 | lookatvector.z = 0.0; 90 | lookatvector = lookatvector.normalize(); 91 | lookatvector *= factor; 92 | self.move_by(lookatvector); 93 | } 94 | 95 | /// Method to call when **backward movement** is needed 96 | 97 | /// `factor` is a factor to scale the movement speed 98 | 99 | /// `factor` has to be positive for backward movement 100 | pub fn move_backward(&mut self, factor: f32) { 101 | let mut lookatvector = self.get_look_at_vector(); 102 | lookatvector.z = 0.0; 103 | lookatvector = lookatvector.normalize(); 104 | lookatvector *= -factor; 105 | self.move_by(lookatvector); 106 | } 107 | 108 | /// Method to call when **left movement** is needed 109 | 110 | /// `factor` is a factor to scale the movement speed 111 | 112 | /// `factor` has to be positive for left movement 113 | pub fn move_left(&mut self, factor: f32) { 114 | let mut lookatvector = self.get_look_at_vector(); 115 | lookatvector.z = 0.0; 116 | lookatvector = lookatvector.normalize(); 117 | // Get the orthogonal 2d-vector, which is 90 degrees to the left 118 | let mut move_dir = Vector3f::new(-lookatvector.y, lookatvector.x, 0.0); 119 | move_dir *= factor; 120 | self.move_by(move_dir); 121 | } 122 | 123 | /// Method to call when **right movement** is needed 124 | 125 | /// `factor` is a factor to scale the movement speed 126 | 127 | /// `factor` has to be positive for right movement 128 | pub fn move_right(&mut self, factor: f32) { 129 | let mut lookatvector = self.get_look_at_vector(); 130 | lookatvector.z = 0.0; 131 | lookatvector = lookatvector.normalize(); 132 | // Get the orthogonal 2d-vector, which is 90 degrees to the left 133 | let mut move_dir = Vector3f::new(lookatvector.y, -lookatvector.x, 0.0); 134 | move_dir *= factor; 135 | self.move_by(move_dir); 136 | } 137 | 138 | /// Method to call when **upward movement** is needed 139 | 140 | /// `factor` is a factor to scale the movement speed 141 | 142 | /// `factor` has to be positive for upward movement 143 | pub fn move_up(&mut self, factor: f32) { 144 | self.move_by(Vector3f::new(0.0, 0.0, factor)); 145 | } 146 | 147 | /// Method to call when **downward movement** is needed 148 | 149 | /// `factor` is a factor to scale the movement speed 150 | 151 | /// `factor` has to be positive for downward movement 152 | pub fn move_down(&mut self, factor: f32) { 153 | self.move_by(Vector3f::new(0.0, 0.0, -factor)); 154 | } 155 | 156 | /// Changes `theta` and `phi` to essentially change the direction the 157 | /// camera looks 158 | pub fn change_dir(&mut self, theta_diff: f32, phi_diff: f32) { 159 | if self.theta < 0.1 { 160 | if theta_diff > 0.0 { 161 | // all the way UP, theta will not go any lower 162 | self.theta += theta_diff; 163 | } 164 | } else if self.theta > consts::PI - 0.1 { 165 | if theta_diff < 0.0 { 166 | // all the way DOWN, theta will not go any higher 167 | self.theta += theta_diff; 168 | } 169 | } else { 170 | self.theta += theta_diff; 171 | } 172 | self.phi += phi_diff; 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /client/src/control_switcher.rs: -------------------------------------------------------------------------------- 1 | use super::camera::*; 2 | use super::event_manager::*; 3 | use ghost::Ghost; 4 | use glium::glutin::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; 5 | use player::Player; 6 | 7 | /// Switch between `ghost` and `player` cameras with `G` key 8 | pub struct ControlSwitcher { 9 | player: Player, 10 | ghost: Ghost, 11 | is_ghost: bool, 12 | } 13 | 14 | impl ControlSwitcher { 15 | pub fn new(player: Player, ghost: Ghost) -> Self { 16 | ControlSwitcher { 17 | player: player, 18 | ghost: ghost, 19 | is_ghost: true, 20 | } 21 | } 22 | /// Return current `Camera` 23 | pub fn get_camera(&self) -> Camera { 24 | if self.is_ghost { 25 | self.ghost.get_camera() 26 | } else { 27 | self.player.get_camera() 28 | } 29 | } 30 | 31 | /// Run camera `update` function 32 | pub fn update(&mut self, delta: f32) { 33 | if self.is_ghost { 34 | self.ghost.update(delta); 35 | } else { 36 | self.player.update(delta); 37 | } 38 | } 39 | 40 | /// Switch current camera between `ghost` and `player` 41 | /// Return to original location of `player` 42 | pub fn switch_cam(&mut self) { 43 | if self.is_ghost { 44 | self.player.set_camera(self.ghost.get_camera()); 45 | self.is_ghost = false; 46 | } else { 47 | self.ghost.set_camera(self.player.get_camera()); 48 | self.is_ghost = true; 49 | } 50 | } 51 | } 52 | 53 | /// Listen for `G` 54 | impl EventHandler for ControlSwitcher { 55 | fn handle_event(&mut self, e: &Event) -> EventResponse { 56 | match *e { 57 | Event::WindowEvent { 58 | event: 59 | WindowEvent::KeyboardInput { 60 | input: 61 | KeyboardInput { 62 | state: ElementState::Pressed, 63 | virtual_keycode: Some(VirtualKeyCode::G), 64 | .. 65 | }, 66 | .. 67 | }, 68 | .. 69 | } => { 70 | self.switch_cam(); 71 | EventResponse::Continue 72 | } 73 | 74 | _ => { 75 | if self.is_ghost { 76 | self.ghost.handle_event(e) 77 | } else { 78 | self.player.handle_event(e) 79 | } 80 | } 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /client/src/daytime.rs: -------------------------------------------------------------------------------- 1 | use super::event_manager::*; 2 | use base::math::*; 3 | use glium::glutin::{ElementState, Event, KeyboardInput, VirtualKeyCode, WindowEvent}; 4 | use std::f32::consts; 5 | 6 | #[derive(Debug)] 7 | pub struct DayTime { 8 | time_year: u32, 9 | time_day: u32, 10 | time_on_day: f32, 11 | speed: f32, 12 | } 13 | 14 | // `DEFAULT_TIME_SPEED` is 1.0 so the time goes at normal speed 15 | // `PLUS_TIME_SPEED` is the factor with which the time is sped up, when the 16 | // speed-up key is pressed 17 | const DEFAULT_TIME_SPEED: f32 = 1.0; 18 | const PLUS_TIME_SPEED: f32 = 100.0; 19 | 20 | impl Default for DayTime { 21 | fn default() -> DayTime { 22 | DayTime { 23 | time_year: 0, 24 | time_day: 0, 25 | time_on_day: DAY_LENGTH / 3.0, 26 | speed: DEFAULT_TIME_SPEED, 27 | } 28 | } 29 | } 30 | 31 | // 360 == Mittag 32 | const DAY_LENGTH: f32 = 720.0; 33 | const YEAR_LENGTH: u32 = 12; 34 | 35 | // lengthens the day by a static offset 36 | const DAY_LENGTHER: f32 = 0.0; 37 | 38 | // Distance of the path of the sun to the player 39 | const SUN_DISTANCE: f32 = 300.0; 40 | 41 | impl DayTime { 42 | pub fn set_time(&mut self, time_year: u32, time_day: u32, time_on_day: f32) { 43 | self.time_year = time_year; 44 | self.time_day = time_day; 45 | self.time_on_day = time_on_day; 46 | self.speed = DEFAULT_TIME_SPEED; 47 | } 48 | 49 | // Bei tag sind die RGBWerte bei 3000, leicht rötlich 50 | // Bei nacht sind sie 0 51 | pub fn get_sun_color(&self) -> Vector3f { 52 | let mut vec = Vector3f::new(0.0, 0.0, 0.0); 53 | 54 | let offset_factor = 1.1; 55 | let max = 30.0 / offset_factor; 56 | 57 | let factor = if self.time_on_day <= (DAY_LENGTH / 2.0) { 58 | max * (self.time_on_day / (DAY_LENGTH / 2.0)) 59 | } else { 60 | max - (max * ((self.time_on_day - DAY_LENGTH / 2.0) / (DAY_LENGTH / 2.0))) 61 | }; 62 | 63 | vec.x = offset_factor * factor; 64 | vec.y = factor; 65 | vec.z = factor; 66 | 67 | vec 68 | } 69 | 70 | // Bei tag ist die Helligkeit 100, leicht bläulich 71 | // Bei nacht ist die Helligkeit 0 72 | pub fn get_sky_light(&self) -> Vector3f { 73 | let mut vec = Vector3f::new(0.0, 0.0, 0.0); 74 | 75 | let offset_factor = 1.1; 76 | let max = 1.0 / offset_factor; 77 | 78 | let mut factor = if self.time_on_day <= (DAY_LENGTH / 2.0) { 79 | max * (self.time_on_day / (DAY_LENGTH / 2.0)) 80 | } else { 81 | max - (max * 0.5 * ((self.time_on_day - DAY_LENGTH / 2.0) / (DAY_LENGTH / 2.0))) 82 | }; 83 | 84 | if factor < 0.4 { 85 | factor = 0.4; 86 | } 87 | 88 | vec.x = factor; 89 | vec.y = factor; 90 | vec.z = offset_factor * factor; 91 | 92 | vec 93 | } 94 | 95 | pub fn get_time_year(&self) -> u32 { 96 | self.time_year 97 | } 98 | 99 | pub fn get_time_day(&self) -> u32 { 100 | self.time_day 101 | } 102 | 103 | pub fn get_time_on_day(&self) -> f32 { 104 | self.time_on_day 105 | } 106 | 107 | /// Updates time with the use of `delta` as additionally passed time 108 | /// `DAY_LENGTH` defines the length of a day in real-life seconds 109 | /// `YEAR_LENGTH` defines the length of a year in `DAY_LENGTH`s 110 | pub fn update(&mut self, delta: f32) { 111 | // Output of Time 112 | debug!( 113 | "Year: {} Day: {} Time: {}", 114 | self.time_year, self.time_day, self.time_on_day 115 | ); 116 | 117 | // Checks if one day has passed 118 | self.time_on_day += delta * self.speed; 119 | if (self.time_on_day) >= DAY_LENGTH { 120 | self.time_on_day -= DAY_LENGTH; // Removes one day from time_on_day 121 | self.time_day += 1; 122 | if (self.time_day) >= YEAR_LENGTH { 123 | self.time_day = 0; 124 | self.time_year += 1; 125 | } 126 | } 127 | } 128 | 129 | /// returns the position of the sun corresponding to time 130 | /// only mid summer 131 | pub fn get_sun_position(&self) -> Point3f { 132 | let half_year = YEAR_LENGTH as f32 / 2.0; 133 | let half_day = DAY_LENGTH as f32 / 2.0; 134 | 135 | let theta; 136 | let phi; 137 | 138 | let mut month_diff = self.time_day as f32 - half_year; 139 | if month_diff < 0.0 { 140 | month_diff *= -1.0 141 | } 142 | 143 | if self.time_on_day < half_day { 144 | // pre noon 145 | // sun rising 146 | theta = consts::PI - consts::PI * (self.time_on_day / half_day); 147 | phi = 0.0; 148 | } else { 149 | // after noon 150 | // sun going down 151 | theta = consts::PI * ((self.time_on_day - half_day) / half_day); 152 | phi = consts::PI; 153 | } 154 | 155 | // for debugging 156 | // info!("THETA: {} PHI: {}", theta, phi); 157 | 158 | // returns sun position in cartesian coordinates 159 | // uses `YEAR_LENGTH` and the current day of the year (month) to influence the 160 | // path the sun moves on 161 | let pos = Vector3f::new( 162 | theta.sin() * phi.cos(), 163 | theta.sin() * phi.sin() + month_diff / (0.75 * YEAR_LENGTH as f32), 164 | theta.cos() - month_diff / (0.75 * YEAR_LENGTH as f32) + DAY_LENGTHER, 165 | ) 166 | .normalize() 167 | * SUN_DISTANCE; 168 | 169 | Point3f::new(pos.x, pos.y, pos.z) 170 | } 171 | 172 | /// returns the Vector3f for the directional sunlight 173 | pub fn get_sun_light_vector(&self) -> Vector3f { 174 | Vector3f::new(0.0, 0.0, 0.0) - self.get_sun_position().to_vec().normalize() 175 | } 176 | } 177 | 178 | /// Handler to speed up time with use of '+' key 179 | impl EventHandler for DayTime { 180 | fn handle_event(&mut self, e: &Event) -> EventResponse { 181 | let input = match e { 182 | Event::WindowEvent { 183 | event: WindowEvent::KeyboardInput { input, .. }, 184 | .. 185 | } => input, 186 | _ => return EventResponse::NotHandled, 187 | }; 188 | 189 | match input { 190 | KeyboardInput { 191 | state: ElementState::Pressed, 192 | virtual_keycode: Some(VirtualKeyCode::Add), 193 | .. 194 | } => { 195 | self.speed = PLUS_TIME_SPEED; 196 | EventResponse::Continue 197 | } 198 | KeyboardInput { 199 | state: ElementState::Released, 200 | virtual_keycode: Some(VirtualKeyCode::Add), 201 | .. 202 | } => { 203 | self.speed = DEFAULT_TIME_SPEED; 204 | EventResponse::Continue 205 | } 206 | _ => EventResponse::NotHandled, 207 | } 208 | } 209 | } 210 | -------------------------------------------------------------------------------- /client/src/event_manager.rs: -------------------------------------------------------------------------------- 1 | use glium::glutin::{Event, EventsLoop, KeyboardInput, VirtualKeyCode, WindowEvent}; 2 | 3 | /// Every event receiver has to return a response for each event received. 4 | #[derive(Clone, Copy, PartialEq, Eq, Debug)] 5 | pub enum EventResponse { 6 | /// The event was not handled at all 7 | NotHandled, 8 | /// The event was handled but should be forwarded to other receivers, too 9 | Continue, 10 | /// The event was handled and should *not* be forwarded to other receivers 11 | Break, 12 | /// In response to the event, the program should terminate 13 | Quit, 14 | } 15 | 16 | pub struct EventManager { 17 | events_loop: EventsLoop, 18 | } 19 | 20 | impl EventManager { 21 | pub fn new(events_loop: EventsLoop) -> Self { 22 | EventManager { events_loop } 23 | } 24 | 25 | pub fn poll_events(&mut self, mut handlers: Vec<&mut dyn EventHandler>) -> EventResponse { 26 | let mut quit = false; 27 | self.events_loop.poll_events(|ev| { 28 | for i in 0..handlers.len() { 29 | let response = handlers[i].handle_event(&ev); 30 | match response { 31 | EventResponse::NotHandled | EventResponse::Continue => (), 32 | EventResponse::Break => break, 33 | EventResponse::Quit => quit = true, 34 | } 35 | } 36 | }); 37 | 38 | if quit { 39 | EventResponse::Quit 40 | } else { 41 | EventResponse::NotHandled 42 | } 43 | } 44 | } 45 | 46 | pub trait EventHandler { 47 | fn handle_event(&mut self, e: &Event) -> EventResponse; 48 | } 49 | 50 | /// Handler that handles the closing of the window 51 | pub struct CloseHandler; 52 | 53 | /// handle_event function of CloseHandler 54 | 55 | /// Windows can be closed by: 56 | 57 | /// *clicking the 'X' on the upper edge of the window 58 | 59 | /// *pressing 'Escape' 60 | impl EventHandler for CloseHandler { 61 | fn handle_event(&mut self, e: &Event) -> EventResponse { 62 | let e = match e { 63 | Event::WindowEvent { event, .. } => event, 64 | _ => return EventResponse::NotHandled, 65 | }; 66 | 67 | match e { 68 | WindowEvent::CloseRequested 69 | | WindowEvent::KeyboardInput { 70 | input: 71 | KeyboardInput { 72 | virtual_keycode: Some(VirtualKeyCode::Escape), 73 | .. 74 | }, 75 | .. 76 | } => EventResponse::Quit, 77 | _ => EventResponse::NotHandled, 78 | } 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /client/src/frustum.rs: -------------------------------------------------------------------------------- 1 | use base::math::*; 2 | use std::f64::consts::PI; 3 | enum SIDE { 4 | Top, 5 | Bottom, 6 | Left, 7 | Right, 8 | NearP, 9 | FarP, 10 | } 11 | pub enum LOCATION { 12 | Inside, 13 | Outside, 14 | Intersect, 15 | } 16 | 17 | // f64 for high accuracy #makef64greatagain 18 | const ANG2RAD: f64 = (PI / 180.); 19 | const RAD2ANG: f64 = (180. / PI); 20 | 21 | struct Plane { 22 | points: [Point3f; 3], 23 | d: f32, 24 | normal: Vector3f, 25 | } 26 | 27 | impl Plane { 28 | // is used to create plane with dummy values 29 | // use setPoint3fs for right initialization 30 | pub fn new() -> Plane { 31 | Plane { 32 | points: [ 33 | Point3f::new(0., 0., 0.), 34 | Point3f::new(1., 1., 1.), 35 | Point3f::new(3., 2., 2.), 36 | ], 37 | d: 0., 38 | normal: Vector3f::new(0., 1., -1.), 39 | } 40 | } 41 | 42 | pub fn set_point3fs(&mut self, x: Vector3f, y: Vector3f, z: Vector3f) { 43 | self.points[0] = Point3f { 44 | x: x.x, 45 | y: x.y, 46 | z: x.z, 47 | }; 48 | self.points[1] = Point3f { 49 | x: y.x, 50 | y: y.y, 51 | z: y.z, 52 | }; 53 | self.points[2] = Point3f { 54 | x: z.x, 55 | y: z.y, 56 | z: z.z, 57 | }; 58 | 59 | let xy = y - x; 60 | let xz = z - x; 61 | 62 | self.normal = xy.cross(xz).normalize(); 63 | self.d = -(self.normal.x * self.points[0].x 64 | + self.normal.y * self.points[0].y 65 | + self.normal.z * self.points[0].z); 66 | } 67 | 68 | pub fn distance(&self, p: &Point3f) -> f32 { 69 | let top = self.normal.x * p.x + self.normal.y * p.y + self.normal.z * p.z + self.d; 70 | let c = |x| x * x; 71 | let bottom = (c(self.normal.x) + c(self.normal.y) + c(self.normal.z)).sqrt(); 72 | // info!("distance {:?}", top / bottom); 73 | top / bottom 74 | } 75 | } 76 | 77 | pub struct Frustum { 78 | planes: [Plane; 6], 79 | angle: f32, 80 | ratio: f32, 81 | near: f32, 82 | far: f32, 83 | nearheight: f32, 84 | nearwidth: f32, 85 | farheight: f32, 86 | farwidth: f32, 87 | } 88 | 89 | impl Frustum { 90 | // initialization stuff contains dummys use 91 | // set_cam_internals and set_cam_def to initialize the frustum 92 | pub fn new() -> Frustum { 93 | let ps = [ 94 | Plane::new(), 95 | Plane::new(), 96 | Plane::new(), 97 | Plane::new(), 98 | Plane::new(), 99 | Plane::new(), 100 | ]; 101 | Frustum { 102 | planes: ps, 103 | angle: 0.0, 104 | ratio: 0.75, 105 | near: 10.0, 106 | far: 300.0, 107 | nearheight: 800.0, 108 | nearwidth: 600.0, 109 | farheight: 1920.0, 110 | farwidth: 1080.0, 111 | } 112 | } 113 | 114 | pub fn set_cam_internals(&mut self, angle: f32, ratio: f32, near: f32, far: f32) { 115 | self.angle = angle; 116 | self.ratio = ratio; 117 | self.near = near; 118 | self.far = far; 119 | 120 | let tan: f32 = ((ANG2RAD * self.angle as f64 * 0.5).tan()) as f32; 121 | 122 | self.nearheight = self.near * tan; 123 | self.nearwidth = self.nearheight * ratio; 124 | self.farheight = self.far * tan; 125 | self.farwidth = self.farheight * ratio; 126 | } 127 | 128 | // camera pos is a vector to use operators like 129 | // -/+ (no ops for point3f - vector3f) 130 | // note replace with more efficent later on 131 | pub fn set_cam_def(&mut self, pos: Point3f, look_at: Vector3f, up: Vector3f) { 132 | // axis cause we all want them 133 | let camera_pos = Vector3f::new(pos.x, pos.y, pos.z); 134 | let z = (camera_pos - look_at).normalize(); 135 | let x = (up.cross(z)).normalize(); 136 | let y = (z.cross(x)).normalize(); 137 | 138 | // calc center of [insert near/far plane joke here] 139 | let c = { |x: f32| (camera_pos - z) * (x) }; 140 | 141 | let nc = c(self.near); 142 | let fc = c(self.far); 143 | 144 | // 4 corners of Frustum near plane 145 | let ntl = nc + y * self.nearheight - x * self.nearwidth; 146 | let ntr = nc + y * self.nearheight + x * self.nearwidth; 147 | let nbl = nc - y * self.nearheight - x * self.nearwidth; 148 | let nbr = nc - y * self.nearheight + x * self.nearwidth; 149 | // 4 corners of Frustum far plane 150 | let ftl = fc + y * self.farheight - x * self.farwidth; 151 | let ftr = fc + y * self.farheight + x * self.farwidth; 152 | let fbl = fc - y * self.farheight - x * self.farwidth; 153 | let fbr = fc - y * self.farheight + x * self.farwidth; 154 | 155 | // set planes points are given counter clockwise 156 | let mut c = |s, x, y, z| self.planes[s as usize].set_point3fs(x, y, z); 157 | c(SIDE::Top, ntr, ntl, ftl); 158 | c(SIDE::Bottom, nbl, nbr, fbr); 159 | c(SIDE::Left, ntl, nbl, fbl); 160 | c(SIDE::Right, nbr, ntr, fbr); 161 | c(SIDE::NearP, ntl, ntr, nbr); 162 | c(SIDE::FarP, ftr, ftl, fbl); 163 | } 164 | 165 | pub fn point_in_frustum(&self, p: &Point3f) -> LOCATION { 166 | // If a point is inside the frustum it must be on the right 167 | // side of every plane. 168 | for i in 0..6 { 169 | if self.planes[i].distance(p) < 0.0 { 170 | return LOCATION::Outside; 171 | } 172 | } 173 | LOCATION::Inside 174 | } 175 | 176 | pub fn sphere_in_frustum(&self) { 177 | // TODO if needed 178 | } 179 | 180 | pub fn box_in_frustum(&self, points: [&Point3f; 8]) -> LOCATION { 181 | // If one of the corner points is inside we box needs to be rendered so INSIDE 182 | let mut ins; 183 | let mut out; 184 | for i in 0..6 { 185 | ins = 0; 186 | out = 0; 187 | for k in 0..8 { 188 | if self.planes[i].distance(points[k]) < 0.0 { 189 | out += 1; 190 | if ins != 0 { 191 | break; 192 | } 193 | } else { 194 | ins += 1; 195 | if out != 0 { 196 | break; 197 | } 198 | } 199 | } 200 | // no point inside plane ... OUTSIDE 201 | if ins == 0 { 202 | return LOCATION::Outside; 203 | } else if out != 0 { 204 | return LOCATION::Intersect; 205 | } 206 | } 207 | LOCATION::Inside 208 | } 209 | } 210 | 211 | // simple culling struct 212 | pub struct SimpleCull { 213 | cam_pos: Point3f, 214 | look_at: Vector3f, 215 | fov: f32, 216 | } 217 | 218 | impl SimpleCull { 219 | pub fn new() -> SimpleCull { 220 | SimpleCull { 221 | cam_pos: Point3f::new(0.0, 0.0, 0.0), 222 | look_at: Vector3f::new(0.0, 0.0, 0.0), 223 | fov: 60.0, 224 | } 225 | } 226 | // sets values of struct 227 | pub fn set_up(&mut self, cam_pos: Point3f, look_at: Vector3f, fov: f32) { 228 | self.fov = fov; 229 | let fake_look = look_at.normalize() * -15.; 230 | self.cam_pos = cam_pos + fake_look; 231 | self.look_at = look_at.normalize(); 232 | } 233 | // checks if box is visible 234 | pub fn is_vis(&self, points: [&Point3f; 8]) -> LOCATION { 235 | for i in 0..8 { 236 | let mut look_at_point = points[i] - self.cam_pos; 237 | look_at_point = look_at_point.normalize(); 238 | let res = look_at_point.dot(self.look_at).acos() * RAD2ANG as f32; 239 | if res <= self.fov { 240 | return LOCATION::Inside; 241 | } 242 | } 243 | LOCATION::Outside 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /client/src/game_context.rs: -------------------------------------------------------------------------------- 1 | use super::Config; 2 | use glium::program; 3 | use glium::Display; 4 | use glium::Program; 5 | use std::error::Error; 6 | use std::fs::File; 7 | use std::io::{self, Read}; 8 | 9 | #[derive(Clone)] 10 | pub struct GameContext { 11 | facade: Display, 12 | config: Config, // TODO: we might want to wrap it into `Rc` (performance) 13 | } 14 | 15 | impl GameContext { 16 | pub fn new(facade: Display, config: Config) -> Self { 17 | GameContext { 18 | facade: facade, 19 | config: config, 20 | } 21 | } 22 | 23 | pub fn get_facade(&self) -> &Display { 24 | &self.facade 25 | } 26 | 27 | pub fn get_config(&self) -> &Config { 28 | &self.config 29 | } 30 | 31 | /// Loads vertex and fragment shader automatically to prevent recompiling 32 | /// the application 33 | /// everytime a shader is changed. 34 | pub fn load_program(&self, shader: &str) -> Result> { 35 | fn load_if_present(path: &str) -> Result { 36 | let mut f = File::open(path)?; 37 | let mut buf = String::new(); 38 | f.read_to_string(&mut buf)?; 39 | Ok(buf) 40 | } 41 | 42 | let mut vert = File::open(&format!("client/shader/{}.vert", shader))?; 43 | let mut frag = File::open(&format!("client/shader/{}.frag", shader))?; 44 | 45 | let mut vert_buf = String::new(); 46 | let mut frag_buf = String::new(); 47 | vert.read_to_string(&mut vert_buf)?; 48 | frag.read_to_string(&mut frag_buf)?; 49 | 50 | let (tcs, tes); 51 | if self.config.tessellation { 52 | tcs = load_if_present(&format!("client/shader/{}.tcs", shader)).ok(); 53 | tes = load_if_present(&format!("client/shader/{}.tes", shader)).ok(); 54 | } else { 55 | // Don't even try to load the tessellation shaders if tessellation is off 56 | tcs = None; 57 | tes = None; 58 | } 59 | 60 | let source = program::SourceCode { 61 | vertex_shader: &vert_buf, 62 | tessellation_control_shader: tcs.as_ref().map(|s| s.as_str()), 63 | tessellation_evaluation_shader: tes.as_ref().map(|s| s.as_str()), 64 | geometry_shader: None, 65 | fragment_shader: &frag_buf, 66 | }; 67 | 68 | let prog = Program::new(&self.facade, source); 69 | if let Err(ref e) = prog { 70 | warn!("failed to compile program '{}':\n{}", shader, e); 71 | } 72 | Ok(prog?) 73 | } 74 | 75 | // TODO: `load_post_processing_program` which loads a default vertex shader 76 | } 77 | -------------------------------------------------------------------------------- /client/src/lib.rs: -------------------------------------------------------------------------------- 1 | //! This crate contains functionality for the client only. This is mainly 2 | //! graphics and input handling. 3 | //! 4 | 5 | #![allow(illegal_floating_point_literal_pattern)] 6 | 7 | extern crate base; 8 | extern crate rand; 9 | #[macro_use] 10 | extern crate glium; 11 | extern crate noise; 12 | #[macro_use] 13 | extern crate log; 14 | 15 | mod camera; 16 | mod config; 17 | mod control_switcher; 18 | pub mod daytime; 19 | mod event_manager; 20 | mod frustum; 21 | mod game; 22 | mod game_context; 23 | mod ghost; 24 | mod player; 25 | mod renderer; 26 | pub mod util; 27 | pub mod view; 28 | mod weather; 29 | mod world; 30 | mod world_manager; 31 | 32 | pub use camera::Camera; 33 | pub use config::Config; 34 | pub use daytime::*; 35 | pub use event_manager::*; 36 | pub use frustum::Frustum; 37 | pub use frustum::SimpleCull; 38 | pub use frustum::LOCATION; 39 | pub use game_context::GameContext; 40 | pub use renderer::Renderer; 41 | pub use world_manager::WorldManager; 42 | 43 | use game::Game; 44 | use std::error::Error; 45 | use std::net::SocketAddr; 46 | 47 | pub fn start_game(config: Config, server: SocketAddr) -> Result<(), Box> { 48 | let game = Game::new(config, server)?; 49 | game.run() 50 | } 51 | -------------------------------------------------------------------------------- /client/src/util/mod.rs: -------------------------------------------------------------------------------- 1 | mod to_arr; 2 | 3 | pub use self::to_arr::*; 4 | -------------------------------------------------------------------------------- /client/src/util/to_arr.rs: -------------------------------------------------------------------------------- 1 | use base::math::*; 2 | 3 | /// Helper trait to easily convert various `cgmath` types into array form to 4 | /// use them in glium 5 | pub trait ToArr { 6 | type Output; 7 | 8 | fn to_arr(&self) -> Self::Output; 9 | } 10 | /// Convert a `Matrix4` into a 4x4 array 11 | impl ToArr for Matrix4 { 12 | type Output = [[T; 4]; 4]; 13 | 14 | fn to_arr(&self) -> Self::Output { 15 | (*self).into() 16 | } 17 | } 18 | 19 | /// Convert a `Matrix3` into a 3x3 array 20 | impl ToArr for Matrix3 { 21 | type Output = [[T; 3]; 3]; 22 | 23 | fn to_arr(&self) -> Self::Output { 24 | (*self).into() 25 | } 26 | } 27 | 28 | /// Convert a `Matrix2` into a 2x2 array 29 | impl ToArr for Matrix2 { 30 | type Output = [[T; 2]; 2]; 31 | 32 | fn to_arr(&self) -> Self::Output { 33 | (*self).into() 34 | } 35 | } 36 | 37 | /// Convert a `Vector4` into an array 38 | impl ToArr for Vector4 { 39 | type Output = [T; 4]; 40 | 41 | fn to_arr(&self) -> [T; 4] { 42 | (*self).into() 43 | } 44 | } 45 | 46 | /// Convert a `Vector3` into an array 47 | impl ToArr for Vector3 { 48 | type Output = [T; 3]; 49 | 50 | fn to_arr(&self) -> Self::Output { 51 | (*self).into() 52 | } 53 | } 54 | 55 | /// Convert a `Vector2` into an array 56 | impl ToArr for Vector2 { 57 | type Output = [T; 2]; 58 | 59 | fn to_arr(&self) -> Self::Output { 60 | (*self).into() 61 | } 62 | } 63 | 64 | impl ToArr for Point2 { 65 | type Output = [T; 2]; 66 | 67 | fn to_arr(&self) -> Self::Output { 68 | (*self).into() 69 | } 70 | } 71 | 72 | impl ToArr for Point3 { 73 | type Output = [T; 3]; 74 | 75 | fn to_arr(&self) -> Self::Output { 76 | (*self).into() 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /client/src/view/mod.rs: -------------------------------------------------------------------------------- 1 | mod plant_renderer; 2 | mod plant_view; 3 | mod sky_view; 4 | mod sun_view; 5 | 6 | pub use self::plant_renderer::*; 7 | pub use self::plant_view::*; 8 | pub use self::sky_view::*; 9 | pub use self::sun_view::*; 10 | -------------------------------------------------------------------------------- /client/src/view/plant_renderer.rs: -------------------------------------------------------------------------------- 1 | use glium::Program; 2 | use std::rc::Rc; 3 | use GameContext; 4 | 5 | pub struct PlantRenderer { 6 | program: Program, 7 | shadow_program: Program, 8 | context: Rc, 9 | } 10 | 11 | impl PlantRenderer { 12 | pub fn new(context: Rc) -> Self { 13 | let program = if context.get_config().tessellation { 14 | context.load_program("plants") 15 | } else { 16 | context.load_program("plants_notess") 17 | }; 18 | 19 | PlantRenderer { 20 | program: program.unwrap(), 21 | shadow_program: context.load_program("plant_shadow").unwrap(), 22 | context: context, 23 | } 24 | } 25 | 26 | pub fn program(&self) -> &Program { 27 | &self.program 28 | } 29 | 30 | pub fn shadow_program(&self) -> &Program { 31 | &self.shadow_program 32 | } 33 | 34 | pub fn context(&self) -> &Rc { 35 | &self.context 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /client/src/view/plant_view.rs: -------------------------------------------------------------------------------- 1 | use super::PlantRenderer; 2 | use base::gen::seeded_rng; 3 | use base::math::*; 4 | use base::prop::plant::ControlPoint; 5 | use base::prop::plant::{Plant, Tree}; 6 | use base::world::ChunkIndex; 7 | use glium::backend::Facade; 8 | use glium::index::PrimitiveType; 9 | use glium::texture::Texture2d; 10 | use glium::uniforms::SamplerWrapFunction; 11 | use glium::{self, BackfaceCullingMode, DepthTest, DrawParameters, IndexBuffer, VertexBuffer}; 12 | use std::collections::HashMap; 13 | use std::rc::Rc; 14 | use util::ToArr; 15 | use Camera; 16 | use DayTime; 17 | 18 | /// Graphical representation of a 'base::Plant' 19 | pub struct PlantView { 20 | vertices: VertexBuffer, 21 | instances: HashMap>, 22 | instance_buf: VertexBuffer, 23 | indices: IndexBuffer, 24 | shadow_indices: IndexBuffer, 25 | renderer: Rc, 26 | } 27 | 28 | #[derive(Copy, Clone)] 29 | pub struct Instance { 30 | offset: [f32; 3], 31 | } 32 | implement_vertex!(Instance, offset); 33 | 34 | impl PlantView { 35 | // pub fn from_plant( pos: Point3f, 36 | pub fn from_plant(plant: &Plant, renderer: Rc, facade: &F) -> Self { 37 | let mut indices = Vec::new(); 38 | let mut vertices = Vec::new(); 39 | match *plant { 40 | Plant::Tree(Tree { 41 | ref branches, 42 | trunk_color, 43 | leaf_color, 44 | }) => { 45 | for branch in branches { 46 | let color = if branch.is_trunk { 47 | trunk_color 48 | } else { 49 | leaf_color 50 | }; 51 | gen_branch_buffer(&branch.points, &mut vertices, &mut indices, color); 52 | } 53 | } 54 | }; 55 | 56 | PlantView { 57 | vertices: VertexBuffer::new(facade, &vertices).unwrap(), 58 | instances: HashMap::new(), 59 | instance_buf: VertexBuffer::new(facade, &[]).unwrap(), 60 | indices: IndexBuffer::new( 61 | facade, 62 | PrimitiveType::Patches { 63 | vertices_per_patch: 3, 64 | }, 65 | &indices, 66 | ) 67 | .unwrap(), 68 | shadow_indices: IndexBuffer::new(facade, PrimitiveType::TrianglesList, &indices) 69 | .unwrap(), 70 | renderer: renderer, 71 | } 72 | } 73 | 74 | pub fn add_instance_from_pos(&mut self, chunk_pos: ChunkIndex, pos: Point3f) { 75 | self.instances 76 | .entry(chunk_pos) 77 | .or_insert(Vec::new()) 78 | .push(Instance { 79 | offset: pos.to_arr(), 80 | }); 81 | 82 | self.update_instance_buffer(); 83 | } 84 | 85 | pub fn remove_instance_at_pos(&mut self, chunk_pos: ChunkIndex) { 86 | self.instances.remove(&chunk_pos); 87 | 88 | self.update_instance_buffer(); 89 | } 90 | 91 | fn update_instance_buffer(&mut self) { 92 | let mut tmp_instances = Vec::new(); 93 | for inst_vec in self.instances.values() { 94 | for inst in inst_vec { 95 | tmp_instances.push(*inst); 96 | } 97 | } 98 | self.instance_buf = 99 | VertexBuffer::new(self.renderer.context().get_facade(), &tmp_instances).unwrap(); 100 | } 101 | 102 | pub fn draw_shadow(&self, surface: &mut S, camera: &Camera) { 103 | let uniforms = uniform! { 104 | proj_matrix: camera.proj_matrix().to_arr(), 105 | view_matrix: camera.view_matrix().to_arr(), 106 | camera_pos: camera.position.to_arr(), 107 | }; 108 | 109 | let params = DrawParameters { 110 | depth: glium::Depth { 111 | write: true, 112 | test: DepthTest::IfLess, 113 | ..Default::default() 114 | }, 115 | backface_culling: BackfaceCullingMode::CullClockwise, 116 | multisampling: true, 117 | ..Default::default() 118 | }; 119 | 120 | surface 121 | .draw( 122 | (&self.vertices, self.instance_buf.per_instance().unwrap()), 123 | &self.shadow_indices, 124 | &self.renderer.shadow_program(), 125 | &uniforms, 126 | ¶ms, 127 | ) 128 | .unwrap(); 129 | } 130 | 131 | pub fn draw( 132 | &self, 133 | surface: &mut S, 134 | camera: &Camera, 135 | shadow_map: &Texture2d, 136 | depth_view_proj: &Matrix4, 137 | daytime: &DayTime, 138 | sun_dir: Vector3f, 139 | ) { 140 | let uniforms = uniform! { 141 | proj_matrix: camera.proj_matrix().to_arr(), 142 | view_matrix: camera.view_matrix().to_arr(), 143 | camera_pos: camera.position.to_arr(), 144 | shadow_map: shadow_map.sampled().wrap_function(SamplerWrapFunction::Clamp), 145 | depth_view_proj: depth_view_proj.to_arr(), 146 | sun_dir: sun_dir.to_arr(), 147 | sun_color: daytime.get_sun_color().to_arr(), 148 | sky_light: daytime.get_sky_light().to_arr(), 149 | }; 150 | 151 | let params = DrawParameters { 152 | depth: glium::Depth { 153 | write: true, 154 | test: DepthTest::IfLess, 155 | ..Default::default() 156 | }, 157 | backface_culling: BackfaceCullingMode::CullCounterClockwise, 158 | ..Default::default() 159 | }; 160 | 161 | let indices = if self.renderer.context().get_config().tessellation { 162 | &self.indices 163 | } else { 164 | &self.shadow_indices 165 | }; 166 | 167 | surface 168 | .draw( 169 | (&self.vertices, self.instance_buf.per_instance().unwrap()), 170 | indices, 171 | &self.renderer.program(), 172 | &uniforms, 173 | ¶ms, 174 | ) 175 | .unwrap(); 176 | } 177 | } 178 | 179 | /// Vertex type used to render plants/trees. 180 | #[derive(Debug, Copy, Clone)] 181 | pub struct Vertex { 182 | pub position: [f32; 3], 183 | pub color: [f32; 3], 184 | pub normal: [f32; 3], 185 | } 186 | 187 | implement_vertex!(Vertex, position, color, normal); 188 | 189 | /// generates VertexBuffer and IndexBuffer for Plants 190 | fn gen_branch_buffer( 191 | old_cps: &[ControlPoint], 192 | vertices: &mut Vec, 193 | indices: &mut Vec, 194 | color: Vector3f, 195 | ) { 196 | let old_index_offset = vertices.len() as u32; 197 | let cps = { 198 | let mut cps = vec![old_cps[0]]; 199 | cps.extend_from_slice(old_cps); 200 | cps.push(*old_cps.last().unwrap()); 201 | cps 202 | }; 203 | 204 | for window in cps.windows(3) { 205 | let prev_cp = window[0]; 206 | let curr_cp = window[1]; 207 | let next_cp = window[2]; 208 | let dir = prev_cp.point - next_cp.point; 209 | 210 | for curr_point in &get_points_from_vector(dir) { 211 | vertices.push(Vertex { 212 | position: (curr_cp.point + curr_point * curr_cp.diameter).to_arr(), 213 | color: color.to_arr(), 214 | normal: curr_point.to_arr(), 215 | }); 216 | } 217 | } 218 | 219 | for offset in 0..(old_cps.len() as u32) - 1 { 220 | let offset = offset * 3; 221 | let segment_indices = [0, 1, 3, 4, 3, 1, 1, 2, 4, 5, 4, 2, 2, 0, 5, 3, 5, 0]; 222 | 223 | indices.extend( 224 | segment_indices 225 | .iter() 226 | .map(|i| i + offset + old_index_offset), 227 | ); 228 | } 229 | 230 | let vert_len = vertices.len() as u32; 231 | indices.extend_from_slice(&[vert_len - 3, vert_len - 2, vert_len - 1]); 232 | } 233 | 234 | /// generates 3 normalized vectors perpendicular to the given vector 235 | fn get_points_from_vector(vector: Vector3f) -> [Vector3f; 3] { 236 | let ortho = random_vec_with_angle( 237 | &mut seeded_rng(0x2651aa465abded, (), ()), 238 | vector.normalize(), 239 | 90.0, 240 | ); 241 | let rot = Basis3::from_axis_angle(vector.normalize(), Deg::new(120.0).into()); 242 | let v0 = rot.rotate_vector(ortho); 243 | let v1 = rot.rotate_vector(v0); 244 | 245 | [ortho.normalize(), v0.normalize(), v1.normalize()] 246 | } 247 | -------------------------------------------------------------------------------- /client/src/view/sky_view.rs: -------------------------------------------------------------------------------- 1 | use base::gen::seeded_rng; 2 | use base::math::*; 3 | use glium::draw_parameters::DepthTest; 4 | use glium::index::PrimitiveType; 5 | use glium::texture::Texture2d; 6 | use glium::{self, DrawParameters, IndexBuffer, Program, VertexBuffer}; 7 | use noise::{open_simplex2, PermutationTable}; 8 | use rand::Rand; 9 | use std::rc::Rc; 10 | use util::ToArr; 11 | use Camera; 12 | use DayTime; 13 | use GameContext; 14 | 15 | pub struct SkyView { 16 | vertex_buffer: VertexBuffer, 17 | index_buffer: IndexBuffer, 18 | program: Program, 19 | sun_position: Point3f, 20 | star_map: Texture2d, 21 | } 22 | 23 | impl SkyView { 24 | pub fn new(context: Rc) -> Self { 25 | const SKYDOME_SIZE: f32 = 500.0; 26 | let raw_vertex_buffer = vec![ 27 | // a: part of xy-plane 28 | Vertex { 29 | i_position: [0.0, -SKYDOME_SIZE, 0.0], 30 | i_unit_coords: [0.0, -1.0, 0.0], 31 | }, 32 | // b: part of xy-plane 33 | Vertex { 34 | i_position: [SKYDOME_SIZE, 0.0, 0.0], 35 | i_unit_coords: [1.0, 0.0, 0.0], 36 | }, 37 | // c: part of xy-plane 38 | Vertex { 39 | i_position: [0.0, SKYDOME_SIZE, 0.0], 40 | i_unit_coords: [0.0, 1.0, 0.0], 41 | }, 42 | // d: part of xy-plane 43 | Vertex { 44 | i_position: [-SKYDOME_SIZE, 0.0, 0.0], 45 | i_unit_coords: [-1.0, 0.0, 0.0], 46 | }, 47 | // e: peak of lower hemisphere 48 | Vertex { 49 | i_position: [0.0, 0.0, -SKYDOME_SIZE], 50 | i_unit_coords: [0.0, 0.0, -1.0], 51 | }, 52 | // f: peak of upper hemisphere 53 | Vertex { 54 | i_position: [0.0, 0.0, SKYDOME_SIZE], 55 | i_unit_coords: [0.0, 0.0, 1.0], 56 | }, 57 | ]; 58 | 59 | let vbuf = VertexBuffer::new(context.get_facade(), &raw_vertex_buffer).unwrap(); 60 | 61 | // Indices 62 | // Index-Buffer corresponds to the planes of the octaeder 63 | // [a, b, e] [b, c, e] [c, d, e] [d, a, e] [b, a, f] [a, d, f] [d, c, f] [c, b, 64 | // f] 65 | let raw_index_buffer = [ 66 | 0, 1, 4, 1, 2, 4, 2, 3, 4, 3, 0, 4, 1, 0, 5, 0, 3, 5, 3, 2, 5, 2, 1, 5, 67 | ]; //TrianglesList 68 | let ibuf = IndexBuffer::new( 69 | context.get_facade(), 70 | PrimitiveType::TrianglesList, 71 | &raw_index_buffer, 72 | ) 73 | .unwrap(); 74 | 75 | let seed = 54342354434; 76 | let mut star_rng = seeded_rng(seed, 0, ()); 77 | let star_table = PermutationTable::rand(&mut star_rng); 78 | 79 | // values between 0 and 0.5 80 | const TEX_SIZE: usize = 2048; 81 | let mut v = vec![Vec::new(); TEX_SIZE]; 82 | 83 | for i in 0..TEX_SIZE { 84 | for j in 0..TEX_SIZE * 2 { 85 | v[i].push( 86 | (open_simplex2::(&star_table, &[(i as f32) * 0.1, (j as f32) * 0.1]) 87 | + 0.6) 88 | / 2.0, 89 | ); 90 | } 91 | } 92 | 93 | SkyView { 94 | vertex_buffer: vbuf, 95 | index_buffer: ibuf, 96 | program: context.load_program("skydome").unwrap(), 97 | sun_position: Point3f::new(0.0, 0.0, -1000.0), 98 | star_map: Texture2d::new(context.get_facade(), v).expect("Could not load stars"), 99 | } 100 | } 101 | 102 | pub fn draw_skydome( 103 | &self, 104 | surface: &mut S, 105 | camera: &Camera, 106 | daytime: &DayTime, 107 | ) { 108 | let pos = Point3::new(0.0, 0.0, 0.0); 109 | 110 | // skydome-octaeder position is set to camera position 111 | let view_matrix = 112 | Matrix4::look_at(pos, pos + camera.get_look_at_vector(), Vector3::unit_z()); 113 | 114 | let uniforms = uniform! { 115 | u_proj_matrix: camera.proj_matrix().to_arr(), 116 | u_view_matrix: view_matrix.to_arr(), 117 | u_sun_pos: self.sun_position.to_arr(), 118 | u_star_map: &self.star_map, 119 | u_sun_color: daytime.get_sun_color().to_arr(), 120 | u_sky_light: daytime.get_sky_light().to_arr(), 121 | }; 122 | let params = DrawParameters { 123 | depth: glium::Depth { 124 | write: false, 125 | test: DepthTest::IfLess, 126 | ..Default::default() 127 | }, 128 | ..Default::default() 129 | }; 130 | 131 | surface 132 | .draw( 133 | &self.vertex_buffer, 134 | &self.index_buffer, 135 | &self.program, 136 | &uniforms, 137 | ¶ms, 138 | ) 139 | .unwrap(); 140 | } 141 | 142 | pub fn update(&mut self, pos: Point3f) { 143 | self.sun_position = pos; 144 | } 145 | } 146 | 147 | /// Vertex type used to render chunks (or hex pillars). 148 | #[derive(Debug, Copy, Clone)] 149 | struct Vertex { 150 | pub i_position: [f32; 3], 151 | pub i_unit_coords: [f32; 3], 152 | } 153 | 154 | implement_vertex!(Vertex, i_position, i_unit_coords); 155 | -------------------------------------------------------------------------------- /client/src/view/sun_view.rs: -------------------------------------------------------------------------------- 1 | use base::math::*; 2 | use glium::draw_parameters::DepthTest; 3 | use glium::index::PrimitiveType; 4 | use glium::{self, DrawParameters, IndexBuffer, Program, VertexBuffer}; 5 | use std::rc::Rc; 6 | use util::ToArr; 7 | use Camera; 8 | use GameContext; 9 | 10 | pub struct Sun { 11 | vertex_buffer: VertexBuffer, 12 | index_buffer: IndexBuffer, 13 | program: Program, 14 | position: Point3f, 15 | } 16 | 17 | const SUN_SIZE: f32 = 15.0; 18 | 19 | impl Sun { 20 | pub fn new(context: Rc) -> Self { 21 | let raw_vertex_buffer = vec![ 22 | Vertex { 23 | i_position: [SUN_SIZE, SUN_SIZE, 0.0], 24 | i_unit_coords: [1.0, 1.0, 0.0], 25 | }, 26 | Vertex { 27 | i_position: [-SUN_SIZE, -SUN_SIZE, 0.0], 28 | i_unit_coords: [-1.0, -1.0, 0.0], 29 | }, 30 | Vertex { 31 | i_position: [-SUN_SIZE, SUN_SIZE, 0.0], 32 | i_unit_coords: [-1.0, 1.0, 0.0], 33 | }, 34 | Vertex { 35 | i_position: [SUN_SIZE, -SUN_SIZE, 0.0], 36 | i_unit_coords: [1.0, -1.0, 0.0], 37 | }, 38 | ]; 39 | 40 | let vbuf = VertexBuffer::new(context.get_facade(), &raw_vertex_buffer).unwrap(); 41 | 42 | let raw_index_buffer = [2, 1, 0, 3]; //TrianglesStrip 43 | 44 | let ibuf = IndexBuffer::new( 45 | context.get_facade(), 46 | PrimitiveType::TriangleStrip, 47 | &raw_index_buffer, 48 | ) 49 | .unwrap(); 50 | 51 | Sun { 52 | vertex_buffer: vbuf, 53 | index_buffer: ibuf, 54 | program: context.load_program("sun").unwrap(), 55 | position: Point3f::new(0.0, 0.0, 0.0), 56 | } 57 | } 58 | 59 | pub fn draw_sun(&self, surface: &mut S, camera: &Camera) { 60 | let sun_pos = self.position.to_vec() + camera.position.to_vec(); 61 | let uniforms = uniform! { 62 | u_proj_matrix: camera.proj_matrix().to_arr(), 63 | u_view_matrix: camera.view_matrix().to_arr(), 64 | u_model: Matrix4::from_translation(sun_pos).to_arr(), 65 | sun_pos: sun_pos.normalize().to_arr() , 66 | }; 67 | let params = DrawParameters { 68 | depth: glium::Depth { 69 | write: false, 70 | test: DepthTest::IfLess, 71 | ..Default::default() 72 | }, 73 | ..Default::default() 74 | }; 75 | 76 | surface 77 | .draw( 78 | &self.vertex_buffer, 79 | &self.index_buffer, 80 | &self.program, 81 | &uniforms, 82 | ¶ms, 83 | ) 84 | .unwrap(); 85 | } 86 | 87 | pub fn update(&mut self, pos: Point3f) { 88 | self.position = pos; 89 | } 90 | 91 | pub fn position(&self) -> Point3f { 92 | self.position 93 | } 94 | } 95 | #[derive(Debug, Copy, Clone)] 96 | struct Vertex { 97 | pub i_position: [f32; 3], 98 | pub i_unit_coords: [f32; 3], 99 | } 100 | 101 | implement_vertex!(Vertex, i_position, i_unit_coords); 102 | -------------------------------------------------------------------------------- /client/src/world/chunk_renderer.rs: -------------------------------------------------------------------------------- 1 | use super::normal_converter; 2 | use super::tex_generator; 3 | use base::math::*; 4 | use base::world; 5 | use base::world::ground::GroundMaterial; 6 | use glium::index::PrimitiveType; 7 | use glium::texture::Texture2d; 8 | use glium::{IndexBuffer, Program, VertexBuffer}; 9 | use std::f32::consts; 10 | use std::rc::Rc; 11 | use GameContext; 12 | 13 | pub struct ChunkRenderer { 14 | /// Chunk shader 15 | program: Program, 16 | /// Shadow map shader 17 | shadow_program: Program, 18 | pub noise_sand: Texture2d, 19 | pub noise_snow: Texture2d, 20 | pub noise_grass: Texture2d, 21 | pub noise_stone: Texture2d, 22 | pub noise_dirt: Texture2d, 23 | pub noise_mulch: Texture2d, 24 | /// Normalmaps for fragment shader 25 | pub normal_sand: Texture2d, 26 | pub normal_snow: Texture2d, 27 | pub normal_grass: Texture2d, 28 | pub normal_stone: Texture2d, 29 | pub normal_dirt: Texture2d, 30 | pub normal_mulch: Texture2d, 31 | pub outline: HexagonOutline, 32 | } 33 | 34 | impl ChunkRenderer { 35 | pub fn new(context: Rc) -> Self { 36 | // Get a tupel of a heightmap and texturemap 37 | let sand = tex_generator::create_texture_maps(GroundMaterial::Sand); 38 | let snow = tex_generator::create_texture_maps(GroundMaterial::Snow); 39 | let grass = tex_generator::create_texture_maps(GroundMaterial::Grass); 40 | let stone = tex_generator::create_texture_maps(GroundMaterial::Stone); 41 | let dirt = tex_generator::create_texture_maps(GroundMaterial::Dirt); 42 | let mulch = tex_generator::create_texture_maps(GroundMaterial::Mulch); 43 | 44 | ChunkRenderer { 45 | program: context.load_program("chunk_std").unwrap(), 46 | shadow_program: context.load_program("chunk_shadow").unwrap(), 47 | // Creating a sampler2D from the texturemap 48 | noise_sand: Texture2d::new(context.get_facade(), sand.1).unwrap(), 49 | noise_snow: Texture2d::new(context.get_facade(), snow.1).unwrap(), 50 | noise_grass: Texture2d::new(context.get_facade(), grass.1).unwrap(), 51 | noise_stone: Texture2d::new(context.get_facade(), stone.1).unwrap(), 52 | noise_dirt: Texture2d::new(context.get_facade(), dirt.1).unwrap(), 53 | noise_mulch: Texture2d::new(context.get_facade(), mulch.1).unwrap(), 54 | 55 | // Creating a sampler2D from the heightmap 56 | normal_sand: Texture2d::new( 57 | context.get_facade(), 58 | normal_converter::convert(sand.0, 1.0), 59 | ) 60 | .unwrap(), 61 | normal_snow: Texture2d::new( 62 | context.get_facade(), 63 | normal_converter::convert(snow.0, 1.0), 64 | ) 65 | .unwrap(), 66 | normal_grass: Texture2d::new( 67 | context.get_facade(), 68 | normal_converter::convert(grass.0, 1.0), 69 | ) 70 | .unwrap(), 71 | normal_stone: Texture2d::new( 72 | context.get_facade(), 73 | normal_converter::convert(stone.0, 1.0), 74 | ) 75 | .unwrap(), 76 | normal_dirt: Texture2d::new( 77 | context.get_facade(), 78 | normal_converter::convert(dirt.0, 1.0), 79 | ) 80 | .unwrap(), 81 | normal_mulch: Texture2d::new( 82 | context.get_facade(), 83 | normal_converter::convert(mulch.0, 1.0), 84 | ) 85 | .unwrap(), 86 | outline: HexagonOutline::new(context), 87 | } 88 | } 89 | 90 | /// Gets a reference to the shared chunk shader. 91 | pub fn program(&self) -> &Program { 92 | &self.program 93 | } 94 | 95 | pub fn shadow_program(&self) -> &Program { 96 | &self.shadow_program 97 | } 98 | } 99 | 100 | pub struct HexagonOutline { 101 | program: Program, 102 | vbuf: VertexBuffer, 103 | ibuf: IndexBuffer, 104 | pub pos: Vector3f, 105 | pub display: bool, 106 | } 107 | 108 | impl HexagonOutline { 109 | pub fn new(context: Rc) -> Self { 110 | // Initialize HexagonOutline 111 | let mut vertices = Vec::new(); 112 | let mut indices = Vec::new(); 113 | get_top_hexagon_model(&mut vertices, &mut indices); 114 | get_bottom_hexagon_model(&mut vertices, &mut indices); 115 | get_side_hexagon_model(4, 5, &mut vertices, &mut indices); 116 | get_side_hexagon_model(1, 2, &mut vertices, &mut indices); 117 | get_side_hexagon_model(5, 0, &mut vertices, &mut indices); 118 | get_side_hexagon_model(0, 1, &mut vertices, &mut indices); 119 | get_side_hexagon_model(3, 4, &mut vertices, &mut indices); 120 | get_side_hexagon_model(2, 3, &mut vertices, &mut indices); 121 | 122 | HexagonOutline { 123 | program: context.load_program("outline").unwrap(), 124 | vbuf: VertexBuffer::new(context.get_facade(), &vertices).unwrap(), 125 | ibuf: IndexBuffer::new(context.get_facade(), PrimitiveType::TrianglesList, &indices) 126 | .unwrap(), 127 | pos: Vector3f::new(0.0, 0.0, 50.0), 128 | display: false, 129 | } 130 | } 131 | 132 | pub fn position(&self) -> &Vector3f { 133 | &self.pos 134 | } 135 | 136 | pub fn vertices(&self) -> &VertexBuffer { 137 | &self.vbuf 138 | } 139 | 140 | pub fn indices(&self) -> &IndexBuffer { 141 | &self.ibuf 142 | } 143 | 144 | pub fn program(&self) -> &Program { 145 | &self.program 146 | } 147 | } 148 | 149 | #[derive(Clone, Copy)] 150 | pub struct OutlineVertex { 151 | pub position: [f32; 3], 152 | pub normal: [f32; 3], 153 | pub tex_coords: [f32; 2], 154 | } 155 | 156 | implement_vertex!(OutlineVertex, position, normal, tex_coords); 157 | 158 | /// Calculates one Point-coordinates of a Hexagon 159 | fn hex_corner(size: f32, i: i32) -> (f32, f32) { 160 | let angle_deg = 60.0 * (i as f32) + 30.0; 161 | let angle_rad = (consts::PI / 180.0) * angle_deg; 162 | 163 | (size * angle_rad.cos(), size * angle_rad.sin()) 164 | } 165 | 166 | /// Calculate texture coordinates 167 | fn tex_map(i: i32) -> (f32, f32) { 168 | match i { 169 | 0 => (1.0 - (0.5 - SQRT_3 / 4.0), 0.25), 170 | 1 => (1.0 - (0.5 - SQRT_3 / 4.0), 0.75), 171 | 2 => (0.5, 1.0), 172 | 3 => (0.5 - SQRT_3 / 4.0, 0.75), 173 | 4 => (0.5 - SQRT_3 / 4.0, 0.25), 174 | 5 => (0.5, 0.0), 175 | // TODO: ERROR HANDLING 176 | _ => (0.0, 0.0), 177 | } 178 | } 179 | 180 | /// Calculates the top face of the Hexagon and normals 181 | fn get_top_hexagon_model(vertices: &mut Vec, indices: &mut Vec) { 182 | let cur_len = vertices.len() as u32; 183 | // Corner vertices 184 | for i in 0..6 { 185 | let (x, y) = hex_corner(world::HEX_OUTER_RADIUS, i); 186 | 187 | let (a, b) = tex_map(i); 188 | 189 | vertices.push(OutlineVertex { 190 | position: [x, y, world::PILLAR_STEP_HEIGHT], 191 | normal: [0.0, 0.0, 1.0], 192 | tex_coords: [a, b], 193 | }); 194 | } 195 | 196 | // Central Vertex 197 | vertices.push(OutlineVertex { 198 | position: [0.0, 0.0, world::PILLAR_STEP_HEIGHT], 199 | normal: [0.0, 0.0, 1.0], 200 | tex_coords: [0.5, 0.5], 201 | }); 202 | 203 | indices.append(&mut vec![ 204 | cur_len + 0, 205 | cur_len + 6, 206 | cur_len + 1, 207 | cur_len + 5, 208 | cur_len + 6, 209 | cur_len + 0, 210 | cur_len + 4, 211 | cur_len + 6, 212 | cur_len + 5, 213 | cur_len + 3, 214 | cur_len + 6, 215 | cur_len + 4, 216 | cur_len + 2, 217 | cur_len + 6, 218 | cur_len + 3, 219 | cur_len + 1, 220 | cur_len + 6, 221 | cur_len + 2, 222 | ]); 223 | } 224 | 225 | /// Calculates the bottom face of the Hexagon and the normals 226 | fn get_bottom_hexagon_model(vertices: &mut Vec, indices: &mut Vec) { 227 | let cur_len = vertices.len() as u32; 228 | for i in 0..6 { 229 | let (x, y) = hex_corner(world::HEX_OUTER_RADIUS, i); 230 | 231 | let (a, b) = tex_map(i); 232 | 233 | vertices.push(OutlineVertex { 234 | position: [x, y, 0.0], 235 | normal: [0.0, 0.0, -1.0], 236 | tex_coords: [a, b], 237 | }); 238 | } 239 | 240 | vertices.push(OutlineVertex { 241 | position: [0.0, 0.0, 0.0], 242 | normal: [0.0, 0.0, -1.0], 243 | tex_coords: [0.5, 0.5], 244 | }); 245 | 246 | indices.append(&mut vec![ 247 | cur_len + 1, 248 | cur_len + 6, 249 | cur_len + 0, 250 | cur_len + 0, 251 | cur_len + 6, 252 | cur_len + 5, 253 | cur_len + 5, 254 | cur_len + 6, 255 | cur_len + 4, 256 | cur_len + 4, 257 | cur_len + 6, 258 | cur_len + 3, 259 | cur_len + 3, 260 | cur_len + 6, 261 | cur_len + 2, 262 | cur_len + 2, 263 | cur_len + 6, 264 | cur_len + 1, 265 | ]); 266 | } 267 | 268 | /// Calculates the sides of the Hexagon and normals 269 | fn get_side_hexagon_model( 270 | ind1: i32, 271 | ind2: i32, 272 | vertices: &mut Vec, 273 | indices: &mut Vec, 274 | ) { 275 | let cur_len = vertices.len() as u32; 276 | let (x1, y1) = hex_corner(world::HEX_OUTER_RADIUS, ind1); 277 | let (x2, y2) = hex_corner(world::HEX_OUTER_RADIUS, ind2); 278 | let normal = [y1 + y2, x1 + x2, 0.0]; 279 | 280 | // TODO: tex_coords fix 281 | vertices.push(OutlineVertex { 282 | position: [x1, y1, world::PILLAR_STEP_HEIGHT], 283 | normal: normal, 284 | tex_coords: [0.0, 2.0], 285 | }); 286 | vertices.push(OutlineVertex { 287 | position: [x1, y1, 0.0], 288 | normal: normal, 289 | tex_coords: [0.0, 0.0], 290 | }); 291 | vertices.push(OutlineVertex { 292 | position: [x2, y2, world::PILLAR_STEP_HEIGHT], 293 | normal: normal, 294 | tex_coords: [1.0, 2.0], 295 | }); 296 | vertices.push(OutlineVertex { 297 | position: [x2, y2, 0.0], 298 | normal: normal, 299 | tex_coords: [1.0, 0.0], 300 | }); 301 | 302 | indices.append(&mut vec![ 303 | cur_len + 0, 304 | cur_len + 2, 305 | cur_len + 1, 306 | cur_len + 1, 307 | cur_len + 2, 308 | cur_len + 3, 309 | ]); 310 | } 311 | -------------------------------------------------------------------------------- /client/src/world/mod.rs: -------------------------------------------------------------------------------- 1 | /// Only intergration of children 2 | mod chunk_renderer; 3 | mod chunk_view; 4 | mod normal_converter; 5 | mod tex_generator; 6 | mod world_view; 7 | 8 | pub use self::chunk_renderer::*; 9 | pub use self::world_view::WorldView; 10 | -------------------------------------------------------------------------------- /client/src/world/normal_converter.rs: -------------------------------------------------------------------------------- 1 | use base::math::*; 2 | 3 | /// Converts a height map into a normal map 4 | pub fn convert(map: Vec>, scale: f32) -> Vec> { 5 | let mut normals: Vec> = 6 | vec![vec![(0.0, 0.0, 0.0); map[0].len() as usize]; map.len() as usize]; 7 | let mut n: Vector3f = Vector3f::new(0.0, 0.0, 0.0); 8 | let mut arr; 9 | 10 | for i in 0..map.len() { 11 | for j in 0..map[0].len() { 12 | arr = neighbours(&map, (i as i32, j as i32)); 13 | n.x = scale 14 | * -(arr[8 as usize] - arr[6 as usize] 15 | + 2.0 * (arr[5 as usize] - arr[3 as usize]) 16 | + arr[2 as usize] 17 | - arr[0 as usize]); 18 | n.y = scale 19 | * -(arr[0 as usize] - arr[6 as usize] 20 | + 2.0 * (arr[1 as usize] - arr[7 as usize]) 21 | + arr[2 as usize] 22 | - arr[8 as usize]); 23 | n.z = 1.0; 24 | n = n.normalize(); 25 | normals[i as usize][j as usize] = (n.x, n.y, n.z); 26 | } 27 | } 28 | normals 29 | } 30 | 31 | /// Finds Moore (8-Way) Neighbours for a position in a map 32 | fn neighbours(map: &Vec>, pos: (i32, i32)) -> [f32; 9] { 33 | let ref tmap = *map; 34 | let mut iter = 0; 35 | let mut arr: [f32; 9] = [0.0; 9]; 36 | 37 | for p in 0..3 { 38 | for q in 0..3 { 39 | // println!("{}{}", p, q); 40 | if tmap.get((pos.0 + p - 1) as usize).is_none() 41 | || tmap[(pos.0 + p - 1) as usize] 42 | .get((pos.1 + q - 1) as usize) 43 | .is_none() 44 | { 45 | arr[iter] = tmap[pos.0 as usize][pos.1 as usize]; 46 | } else { 47 | arr[iter] = tmap[(pos.0 + p - 1) as usize][(pos.1 + q - 1) as usize]; 48 | } 49 | iter += 1; 50 | } 51 | } 52 | // info!("{:?}", arr); 53 | arr 54 | } 55 | -------------------------------------------------------------------------------- /client/src/world/tex_generator.rs: -------------------------------------------------------------------------------- 1 | use base::gen::seeded_rng; 2 | use base::noise::{open_simplex2, PermutationTable}; 3 | use base::rand::Rand; 4 | use base::world::ground::GroundMaterial; 5 | 6 | /// Create texture and height map for given ´GroundMaterial´ 7 | pub fn create_texture_maps(ground: GroundMaterial) -> (Vec>, Vec>) { 8 | let mut tex_map = vec![Vec::new(); 256]; 9 | let mut texture_rng = seeded_rng(2, 13, ()); 10 | let table = PermutationTable::rand(&mut texture_rng); 11 | let mut height_map = vec![Vec::new(); 256]; 12 | // matches groundmaterial and sets values, so unnecessary calls for simplex are 13 | // ignored 14 | // value 0-6 are simplex params 15 | // value 7 and 8: 16 | // 0.0 -> not call of open_simplex2 17 | // 0.0 -> call open_simplex2 18 | // value 9 is the ´height_map´ exponent 19 | let long = match ground { 20 | GroundMaterial::Grass => (7.0, 7.0, 9.0, 9.0, 1.0, 1.0, 0.5, 0.0, 3.0), 21 | GroundMaterial::Sand => (0.05, 0.05, 0.015, 0.015, 1.0, 1.0, 0.5, 0.0, 3.3), 22 | GroundMaterial::Snow => (0.5, 0.5, 1.0, 1.0, 2.0, 4.0, 1.0, 0.25, 0.35), 23 | GroundMaterial::Dirt => (0.02, 0.05, 1.0, 1.0, 1.0, 1.0, 0.0, 0.0, 1.5), 24 | GroundMaterial::Stone => (0.05, 0.05, 0.1, 0.1, 1.0, 1.0, 0.5, 0.0, 2.3), 25 | GroundMaterial::JungleGrass => (7.0, 7.0, 9.0, 9.0, 1.0, 1.0, 0.5, 0.0, 3.0), 26 | GroundMaterial::Mulch => (0.25, 0.25, 2.5, 2.5, 1.0, 1.0, 0.5, 0.0, 2.3), 27 | GroundMaterial::Debug => (0.25, 0.25, 2.5, 2.5, 1.0, 1.0, 0.5, 0.0, 2.3), 28 | }; 29 | 30 | // if long.7 or long.8 equals 0.0 then is not open_simplex2 called 31 | for i in 0..256 { 32 | for j in 0..256 { 33 | let e = ((open_simplex2::(&table, &[(i as f32) * long.0, (j as f32) * long.1]) 34 | + 1.0) 35 | / 2.0) 36 | + if long.6 > 0.2 { 37 | long.6 38 | * ((open_simplex2::( 39 | &table, 40 | &[(i as f32) * long.2, (j as f32) * long.3], 41 | ) + 1.0) 42 | / 2.0) 43 | } else { 44 | long.6 45 | } 46 | + if long.7 > 0.2 { 47 | long.7 48 | * ((open_simplex2::( 49 | &table, 50 | &[(i as f32) * long.4, (j as f32) * long.5], 51 | ) + 1.0) 52 | / 2.0) 53 | } else { 54 | long.7 55 | }; 56 | height_map[i].push(e.powf(long.8)); 57 | tex_map[i].push((e, e, e)); 58 | } 59 | } 60 | 61 | (height_map, tex_map) 62 | } 63 | -------------------------------------------------------------------------------- /client/src/world/world_view.rs: -------------------------------------------------------------------------------- 1 | use base::math::*; 2 | use base::prop::Plant; 3 | use base::world::{self, Chunk, ChunkIndex}; 4 | use glium::backend::Facade; 5 | use glium::draw_parameters::BlendingFunction; 6 | use glium::texture::Texture2d; 7 | use glium::{self, DepthTest, DrawParameters, LinearBlendingFactor}; 8 | use Camera; 9 | // use SimpleCull; 10 | use std::collections::HashMap; 11 | use std::rc::Rc; 12 | use util::ToArr; 13 | use view::PlantRenderer; 14 | use world::ChunkRenderer; 15 | use world::HexagonOutline; 16 | use DayTime; 17 | use GameContext; 18 | 19 | pub use view::PlantView; 20 | pub use world::chunk_view::ChunkView; 21 | 22 | /// Graphical representation of the `base::World`. 23 | pub struct WorldView { 24 | chunks: HashMap, 25 | chunk_renderer: Rc, 26 | plant_renderer: Rc, 27 | 28 | pub outline: HexagonOutline, 29 | 30 | plant_views: HashMap, 31 | 32 | plant_list: Vec, 33 | } 34 | 35 | impl WorldView { 36 | pub fn new(context: Rc, plant_list: Vec) -> Self { 37 | let plant_renderer = Rc::new(PlantRenderer::new(context.clone())); 38 | let chunk_renderer = Rc::new(ChunkRenderer::new(context.clone())); 39 | 40 | WorldView { 41 | chunks: HashMap::new(), 42 | chunk_renderer: chunk_renderer, 43 | plant_renderer: plant_renderer, 44 | outline: HexagonOutline::new(context), 45 | plant_views: HashMap::new(), 46 | plant_list: plant_list, 47 | } 48 | } 49 | 50 | pub fn refresh_chunk(&mut self, chunk_pos: ChunkIndex, chunk: &Chunk, facade: &F) { 51 | self.chunks.insert( 52 | chunk_pos, 53 | ChunkView::from_chunk( 54 | chunk, 55 | AxialPoint::new( 56 | chunk_pos.0.q * (1 * world::CHUNK_SIZE as i32), 57 | chunk_pos.0.r * (1 * world::CHUNK_SIZE as i32), 58 | ), 59 | self.chunk_renderer.clone(), 60 | facade, 61 | ), 62 | ); 63 | 64 | for (pillar_pos, pillar) in chunk.pillars() { 65 | for prop in pillar.props() { 66 | let plant_index = prop.plant_index; 67 | let plant = &self.plant_list[plant_index]; 68 | let real_pos = pillar_pos.to_real(); 69 | 70 | let real_chunk_pos = (chunk_pos.0 * world::CHUNK_SIZE as i32).to_real(); 71 | 72 | self.plant_views 73 | .entry(plant_index) 74 | .or_insert(PlantView::from_plant( 75 | plant, 76 | self.plant_renderer.clone(), 77 | facade, 78 | )) 79 | .add_instance_from_pos( 80 | chunk_pos, 81 | Point3f::new( 82 | real_chunk_pos.x + real_pos.x, 83 | real_chunk_pos.y + real_pos.y, 84 | prop.baseline.units() as f32 * world::PILLAR_STEP_HEIGHT, 85 | ), 86 | ); 87 | } 88 | } 89 | } 90 | 91 | pub fn get_chunk_view(&self, index: &ChunkIndex) -> Option<&ChunkView> { 92 | self.chunks.get(&index) 93 | } 94 | 95 | pub fn get_chunk_view_mut(&mut self, index: &ChunkIndex) -> Option<&mut ChunkView> { 96 | self.chunks.get_mut(&index) 97 | } 98 | 99 | pub fn remove_chunk(&mut self, chunk_pos: ChunkIndex) { 100 | self.chunks.remove(&chunk_pos); 101 | for plant_view in self.plant_views.values_mut() { 102 | plant_view.remove_instance_at_pos(chunk_pos); 103 | } 104 | } 105 | 106 | pub fn draw_shadow(&self, surface: &mut S, camera: &Camera) { 107 | let mut chunk_list: Vec<_> = self.chunks.values().collect(); 108 | chunk_list.sort_by_key(|chunk_view| { 109 | chunk_view 110 | .offset() 111 | .to_real() 112 | .distance2(Point2f::new(camera.position.x, camera.position.y)) as u32 113 | }); 114 | 115 | for chunkview in &chunk_list { 116 | chunkview.draw_shadow(surface, camera); 117 | } 118 | 119 | for plantview in self.plant_views.values() { 120 | plantview.draw_shadow(surface, camera); 121 | } 122 | } 123 | 124 | pub fn draw( 125 | &self, 126 | surface: &mut S, 127 | camera: &Camera, 128 | shadow_map: &Texture2d, 129 | depth_view_proj: &Matrix4, 130 | daytime: &DayTime, 131 | sun_dir: Vector3f, 132 | ) { 133 | let mut chunk_list: Vec<_> = self.chunks.values().collect(); 134 | chunk_list.sort_by_key(|chunk_view| { 135 | chunk_view 136 | .offset() 137 | .to_real() 138 | .distance2(Point2f::new(camera.position.x, camera.position.y)) as u32 139 | }); 140 | 141 | for chunkview in &chunk_list { 142 | chunkview.draw( 143 | surface, 144 | camera, 145 | shadow_map, 146 | depth_view_proj, 147 | daytime, 148 | sun_dir, 149 | ); 150 | } 151 | if self.outline.display { 152 | // Draw outline 153 | let outline_params = DrawParameters { 154 | depth: glium::Depth { 155 | write: false, 156 | test: DepthTest::Overwrite, 157 | ..Default::default() 158 | }, 159 | blend: glium::Blend { 160 | color: BlendingFunction::Addition { 161 | source: LinearBlendingFactor::SourceAlpha, 162 | destination: LinearBlendingFactor::OneMinusSourceAlpha, 163 | }, 164 | ..Default::default() 165 | }, 166 | ..Default::default() 167 | }; 168 | 169 | let outline_uniforms = uniform! { 170 | outline_pos: self.outline.position().to_arr(), 171 | proj_matrix: camera.proj_matrix().to_arr(), 172 | view_matrix: camera.view_matrix().to_arr() 173 | }; 174 | 175 | surface 176 | .draw( 177 | self.outline.vertices(), 178 | self.outline.indices(), 179 | self.outline.program(), 180 | &outline_uniforms, 181 | &outline_params, 182 | ) 183 | .unwrap(); 184 | } 185 | 186 | for plantview in self.plant_views.values() { 187 | plantview.draw( 188 | surface, 189 | camera, 190 | shadow_map, 191 | depth_view_proj, 192 | daytime, 193 | sun_dir, 194 | ); 195 | } 196 | } 197 | } 198 | -------------------------------------------------------------------------------- /client/src/world_manager.rs: -------------------------------------------------------------------------------- 1 | use super::GameContext; 2 | use base::math::*; 3 | use base::world::{Chunk, ChunkProvider, World}; 4 | use base::world::{ChunkIndex, CHUNK_SIZE}; 5 | use std::cell::RefMut; 6 | use std::cell::{Ref, RefCell}; 7 | use std::collections::{HashMap, HashSet}; 8 | use std::mem::replace; 9 | use std::rc::Rc; 10 | use std::sync::mpsc::{channel, Receiver, Sender, TryRecvError}; 11 | use std::thread; 12 | use world::WorldView; 13 | 14 | #[derive(Clone)] 15 | pub struct WorldManager { 16 | shared: Rc>, 17 | chunk_requests: Sender, 18 | context: Rc, 19 | } 20 | 21 | struct Shared { 22 | world: World, 23 | world_view: WorldView, 24 | provided_chunks: Receiver<(ChunkIndex, Chunk)>, 25 | sent_requests: HashSet, 26 | load_distance: f32, 27 | player_chunk: ChunkIndex, 28 | } 29 | 30 | impl WorldManager { 31 | pub fn new(provider: Box, game_context: Rc) -> Self { 32 | // Create two channels to send chunk positions and receive chunks. 33 | let (chunk_request_sender, chunk_request_recv) = channel(); 34 | let (chunk_sender, chunk_recv) = channel(); 35 | 36 | let this = WorldManager { 37 | shared: Rc::new(RefCell::new(Shared { 38 | world: World::empty(), 39 | world_view: WorldView::new(game_context.clone(), provider.get_plant_list()), 40 | sent_requests: HashSet::new(), 41 | provided_chunks: chunk_recv, 42 | // TODO: load this from the config! 43 | load_distance: 10.0, 44 | player_chunk: ChunkIndex(AxialPoint::new(0, 0)), 45 | })), 46 | chunk_requests: chunk_request_sender, 47 | context: game_context, 48 | }; 49 | 50 | // Spawn worker thread, which will detach from the main thread. But 51 | // that is no problem: once the last sender is destroyed, the worker 52 | // thread will quit. 53 | thread::spawn(move || { 54 | worker_thread(provider, chunk_request_recv, chunk_sender); 55 | }); 56 | 57 | this.update_player_chunk(); 58 | this 59 | } 60 | 61 | /// Called when the player moves to a different chunk. 62 | /// 63 | /// This unloads all currently loaded chunks that are too far away from the 64 | /// player, and loads all chunks close enough to the player (if they aren't 65 | /// already requested). 66 | fn update_player_chunk(&self) { 67 | let mut shared = self.shared.borrow_mut(); 68 | let load_distance = shared.load_distance; 69 | let player_chunk = shared.player_chunk.0; 70 | let radius = load_distance as i32; 71 | 72 | let is_chunk_in_range = |chunk_pos: AxialPoint| { 73 | let chunk_center = chunk_pos * CHUNK_SIZE as i32 74 | + AxialVector::new(CHUNK_SIZE as i32 / 2, CHUNK_SIZE as i32 / 2); 75 | let player_center = player_chunk * CHUNK_SIZE as i32 76 | + AxialVector::new(CHUNK_SIZE as i32 / 2, CHUNK_SIZE as i32 / 2); 77 | (chunk_center - player_center) 78 | .to_real() 79 | .distance(Vector2f::zero()) 80 | < load_distance * CHUNK_SIZE as f32 81 | }; 82 | 83 | // Load new range 84 | for qd in -radius..radius + 1 { 85 | for rd in -radius..radius + 1 { 86 | let chunk_pos = AxialPoint::new(player_chunk.q + qd, player_chunk.r + rd); 87 | if is_chunk_in_range(chunk_pos) { 88 | let chunk_index = ChunkIndex(chunk_pos); 89 | 90 | if !shared.world.chunks.contains_key(&chunk_index) { 91 | if !shared.sent_requests.contains(&chunk_index) { 92 | self.chunk_requests.send(chunk_index).unwrap(); 93 | shared.sent_requests.insert(chunk_index); 94 | } 95 | } 96 | } 97 | } 98 | } 99 | 100 | // Drop unneeded chunks from world 101 | let chunks = replace(&mut shared.world.chunks, HashMap::new()); 102 | let mut new_chunks = HashMap::new(); 103 | for (index, chunk) in chunks { 104 | let chunk_pos = index.0; 105 | if is_chunk_in_range(chunk_pos) { 106 | // Still in range 107 | new_chunks.insert(index, chunk); 108 | } else { 109 | // Remove 110 | shared.world_view.remove_chunk(index); 111 | } 112 | } 113 | shared.world.chunks = new_chunks; 114 | } 115 | 116 | /// Returns an immutable reference to the world. 117 | /// 118 | /// *Note*: since the world manager uses a `RefCell` to save the world, a 119 | /// `Ref` is returned. But thanks to deref coercions you can use it like a 120 | /// standard reference. 121 | pub fn get_world(&self) -> Ref { 122 | Ref::map(self.shared.borrow(), |shared| &shared.world) 123 | } 124 | 125 | pub fn mut_world(&self) -> RefMut { 126 | RefMut::map(self.shared.borrow_mut(), |shared| &mut shared.world) 127 | } 128 | 129 | /// Returns an immutable reference to the world view. 130 | pub fn get_view(&self) -> Ref { 131 | Ref::map(self.shared.borrow(), |shared| &shared.world_view) 132 | } 133 | 134 | /// Returns an mutable reference to the world view. 135 | pub fn get_mut_view(&self) -> RefMut { 136 | RefMut::map(self.shared.borrow_mut(), |shared| &mut shared.world_view) 137 | } 138 | 139 | pub fn get_context(&self) -> &Rc { 140 | &self.context 141 | } 142 | 143 | /// Starts to generate all chunks within `load_distance` (config parameter) 144 | /// around `pos`. 145 | fn load_world_around(&self, pos: Point2f) { 146 | let axial_pos = AxialPoint::from_real(pos); 147 | let chunk_pos = AxialPoint::new( 148 | axial_pos.q / CHUNK_SIZE as i32, 149 | axial_pos.r / CHUNK_SIZE as i32, 150 | ); 151 | 152 | let mut shared = self.shared.borrow_mut(); 153 | if shared.player_chunk.0 != chunk_pos { 154 | shared.player_chunk = ChunkIndex(chunk_pos); 155 | debug!( 156 | "player moved to chunk {:?} (player at {:?})", 157 | chunk_pos, pos 158 | ); 159 | drop(shared); 160 | 161 | self.update_player_chunk(); 162 | } 163 | } 164 | 165 | /// Applies all queued updated to the actual world. Notably, all generated 166 | /// chunks are added. 167 | pub fn update_world(&self, player_pos: Point3f) { 168 | self.load_world_around(Point2f::new(player_pos.x, player_pos.y)); 169 | 170 | let mut shared = self.shared.borrow_mut(); 171 | let mut changed = false; 172 | 173 | loop { 174 | let (pos, chunk) = match shared.provided_chunks.try_recv() { 175 | Err(TryRecvError::Empty) => { 176 | // No new chunks for us, we will check again next time ;-) 177 | break; 178 | } 179 | Err(TryRecvError::Disconnected) => { 180 | // The worker thread has shut down: this should never happen 181 | // thus we can just panic, too. 182 | error!("chunk providing worker thread shut down"); 183 | panic!("chunk providing worker thread shut down"); 184 | } 185 | Ok(val) => val, 186 | }; 187 | 188 | changed = true; 189 | shared 190 | .world_view 191 | .refresh_chunk(pos, &chunk, self.context.get_facade()); 192 | 193 | shared.sent_requests.remove(&pos); 194 | let res = shared.world.add_chunk(pos, chunk); 195 | if res.is_err() { 196 | warn!("chunk at {:?} already exists!", pos); 197 | } 198 | } 199 | 200 | if changed { 201 | debug!("{} chunks loaded", shared.world.chunks.len()); 202 | } 203 | } 204 | 205 | pub fn recalculate_chunk(&mut self, pos: AxialPoint) { 206 | use std::ops::DerefMut; 207 | 208 | let mut shared_tmp = self.shared.borrow_mut(); 209 | let shared = shared_tmp.deref_mut(); 210 | 211 | let chunk_pos = AxialPoint::new(pos.q / CHUNK_SIZE as i32, pos.r / CHUNK_SIZE as i32); 212 | let index = ChunkIndex(chunk_pos); 213 | 214 | shared.world_view.refresh_chunk( 215 | index, 216 | shared.world.chunk_at(index).unwrap(), 217 | self.context.get_facade(), 218 | ); 219 | } 220 | } 221 | 222 | fn worker_thread( 223 | provider: Box, 224 | commands: Receiver, 225 | chunks: Sender<(ChunkIndex, Chunk)>, 226 | ) { 227 | loop { 228 | let requested_chunk = match commands.recv() { 229 | Err(_) => { 230 | // The other side has hung up, so we can stop working, too. 231 | break; 232 | } 233 | Ok(index) => index, 234 | }; 235 | 236 | debug!( 237 | "chunk provider thread: received request to generate chunk {:?}", 238 | requested_chunk.0 239 | ); 240 | 241 | match provider.load_chunk(requested_chunk) { 242 | Some(chunk) => { 243 | debug!( 244 | "chunk provider thread: chunk at {:?} successfully loaded", 245 | requested_chunk 246 | ); 247 | chunks 248 | .send((requested_chunk, chunk)) 249 | .expect("main thread has hung up"); 250 | } 251 | None => warn!( 252 | "chunk provider thread: failed to load chunk at {:?}", 253 | requested_chunk 254 | ), 255 | } 256 | } 257 | 258 | info!("chunk provider thread: now stopping ...") 259 | } 260 | -------------------------------------------------------------------------------- /plantex-server/main.rs: -------------------------------------------------------------------------------- 1 | extern crate server; 2 | #[macro_use] 3 | extern crate log; 4 | extern crate env_logger; 5 | 6 | use log::LogLevelFilter; 7 | use std::net::TcpListener; 8 | 9 | fn main() { 10 | // Initialize logger (by default error, warning and info logs are shown) 11 | env_logger::LogBuilder::new() 12 | .filter(None, LogLevelFilter::Info) 13 | .parse(&std::env::var("RUST_LOG").unwrap_or("".into())) 14 | .init() 15 | .expect("logger initialization failed"); 16 | 17 | let listener = TcpListener::bind("0.0.0.0:0").unwrap(); 18 | info!("listening on {}", listener.local_addr().unwrap()); 19 | 20 | server::start_server(listener).unwrap(); 21 | } 22 | -------------------------------------------------------------------------------- /plantex.sublime-project: -------------------------------------------------------------------------------- 1 | { 2 | "build_systems": 3 | [ 4 | { 5 | "file_regex": "^[ ]*File \"(...*?)\", line ([0-9]*)", 6 | "name": "Anaconda Python Builder", 7 | "selector": "source.python", 8 | "shell_cmd": "\"python\" -u \"$file\"" 9 | } 10 | ], 11 | "folders": 12 | [ 13 | { 14 | "file_exclude_patterns": 15 | [ 16 | "*.lock", 17 | "LICENSE-*", 18 | "*.rs.bk", 19 | "*.sublime-*" 20 | ], 21 | "folder_exclude_patterns": 22 | [ 23 | "target", 24 | "ci" 25 | ], 26 | "path": "." 27 | } 28 | ], 29 | "settings": 30 | { 31 | "rulers": 32 | [ 33 | 80, 34 | 100 35 | ], 36 | "tab_size": 4, 37 | "translate_tabs_to_spaces": true, 38 | "use_tab_stops": true 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /plantex/main.rs: -------------------------------------------------------------------------------- 1 | extern crate base; 2 | extern crate client; 3 | extern crate env_logger; 4 | #[macro_use] 5 | extern crate log; 6 | extern crate server; 7 | 8 | use log::LogLevelFilter; 9 | use std::io::{self, Write}; 10 | 11 | fn main() { 12 | // Initialize logger (by default error, warning and info logs are shown) 13 | env_logger::LogBuilder::new() 14 | .filter(None, LogLevelFilter::Info) 15 | .parse(&std::env::var("RUST_LOG").unwrap_or("".into())) 16 | .init() 17 | .expect("logger initialization failed"); 18 | 19 | info!("Launching local server"); 20 | let addr = server::start_local_server(); 21 | 22 | info!("~~~~~~~~~~ Plantex started ~~~~~~~~~~"); 23 | 24 | let conf = match client::Config::load_config() { 25 | Ok(v) => v, 26 | Err(e) => { 27 | writeln!(io::stderr(), "{}", e).unwrap(); 28 | return; 29 | } 30 | }; 31 | 32 | let res = client::start_game(conf, addr); 33 | 34 | // Check if any error occured 35 | if res.is_err() { 36 | // Maybe the user disabled all logs, so we mention that the logs 37 | // contain information about the error. 38 | writeln!( 39 | io::stderr(), 40 | "An error occured! Check logs for more information!" 41 | ) 42 | .expect("write to stderr failed"); 43 | std::process::exit(1); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | reorder_imports = true 2 | -------------------------------------------------------------------------------- /server/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | authors = ["Lukas Kalbertodt "] 3 | license = "MIT/Apache-2.0" 4 | name = "server" 5 | version = "0.1.0" 6 | publish = false 7 | 8 | [dependencies] 9 | base = { path = "../base" } 10 | log = "0.3.6" 11 | -------------------------------------------------------------------------------- /server/src/lib.rs: -------------------------------------------------------------------------------- 1 | #[macro_use] 2 | extern crate log; 3 | 4 | mod server; 5 | 6 | use server::Server; 7 | use std::io; 8 | use std::net::{SocketAddr, TcpListener}; 9 | use std::thread; 10 | 11 | /// Starts a Plantex server listening for connections on the given 12 | /// `TcpListener`. 13 | pub fn start_server(listener: TcpListener) -> io::Result<()> { 14 | info!("starting server on {}", listener.local_addr()?); 15 | 16 | let server = Server::new(listener); 17 | server.run() 18 | } 19 | 20 | /// Starts a server in a different thread, returns the address to connect to. 21 | pub fn start_local_server() -> SocketAddr { 22 | let listener = TcpListener::bind("127.0.0.1:0").unwrap(); 23 | let addr = listener.local_addr().unwrap(); 24 | 25 | thread::Builder::new() 26 | .name("Plantex Local Server".to_string()) 27 | .spawn(move || { 28 | start_server(listener).unwrap(); 29 | }) 30 | .unwrap(); 31 | 32 | addr 33 | } 34 | -------------------------------------------------------------------------------- /server/src/server.rs: -------------------------------------------------------------------------------- 1 | use std::io; 2 | use std::net::{TcpListener, TcpStream}; 3 | use std::sync::mpsc::{channel, Receiver, TryRecvError}; 4 | use std::thread; 5 | use std::time::Duration; 6 | 7 | /// A player connected to the server. 8 | struct Player { 9 | conn: TcpStream, 10 | } 11 | 12 | pub struct Server { 13 | /// Receives newly connected player connections from the TCP listener 14 | /// thread. 15 | connections: Receiver, 16 | /// Currently connected players. 17 | players: Vec, 18 | } 19 | 20 | impl Server { 21 | pub fn new(listener: TcpListener) -> Self { 22 | let (sender, recv) = channel(); 23 | 24 | thread::Builder::new() 25 | .name("TCP listener".to_string()) 26 | .spawn(move || { 27 | for conn in listener.incoming() { 28 | match conn { 29 | Ok(conn) => match sender.send(conn) { 30 | Ok(_) => {} 31 | Err(e) => { 32 | error!("tcp listener exiting: {}", e); 33 | } 34 | }, 35 | Err(e) => { 36 | error!("failed to accept client connection: {}", e); 37 | } 38 | } 39 | } 40 | }) 41 | .unwrap(); 42 | 43 | Server { 44 | connections: recv, 45 | players: Vec::new(), 46 | } 47 | } 48 | 49 | /// Runs the server's main loop. 50 | pub fn run(mut self) -> io::Result<()> { 51 | loop { 52 | loop { 53 | match self.connections.try_recv() { 54 | Ok(stream) => { 55 | let id = self.players.len(); 56 | self.players.push(Player { conn: stream }); 57 | self.handle_new_player(id); 58 | } 59 | Err(TryRecvError::Empty) => break, 60 | Err(TryRecvError::Disconnected) => { 61 | info!("tcp listener thread exited, killing server"); 62 | return Ok(()); 63 | } 64 | } 65 | } 66 | 67 | // Sleep for 1/60th of a second 68 | // FIXME Put in a proper rate limit 69 | thread::sleep(Duration::new(0, 1000000 / 60)); 70 | 71 | // self.world_manager.update_world(self.player.get_camera().position); 72 | } 73 | } 74 | 75 | /// Calls a closure with a mutable reference to the given player. 76 | /// 77 | /// If the closure returns `Err`, the player will be disconnected. 78 | fn with_player(&mut self, id: usize, f: F) 79 | where 80 | F: FnOnce(&mut Player) -> io::Result<()>, 81 | { 82 | match f(&mut self.players[id]) { 83 | Ok(()) => return, 84 | Err(e) => { 85 | error!("lost connection to player #{} - {}", id, e); 86 | } 87 | } 88 | 89 | self.players.remove(id); 90 | } 91 | 92 | fn handle_new_player(&mut self, id: usize) { 93 | self.with_player(id, |player| { 94 | info!("client connected from {}", player.conn.peer_addr()?); 95 | Ok(()) 96 | }) 97 | } 98 | } 99 | --------------------------------------------------------------------------------