├── .gitignore ├── Cargo.lock ├── Cargo.toml ├── IDEAS.md ├── LICENSE ├── README.md ├── notcraft-client ├── Cargo.toml └── src │ ├── audio_pool.rs │ ├── client │ ├── audio.rs │ ├── camera.rs │ ├── debug.rs │ ├── input.rs │ ├── loader.rs │ ├── mod.rs │ └── render │ │ ├── mesher │ │ ├── generation.rs │ │ ├── mod.rs │ │ └── tracker.rs │ │ ├── mod.rs │ │ └── renderer.rs │ ├── main.rs │ └── total_float.rs ├── notcraft-common ├── Cargo.toml └── src │ ├── aabb.rs │ ├── codec │ ├── decode.rs │ ├── encode.rs │ └── mod.rs │ ├── debug.rs │ ├── lib.rs │ ├── net │ ├── mod.rs │ └── packet │ │ └── mod.rs │ ├── physics.rs │ ├── transform.rs │ ├── util.rs │ └── world │ ├── chunk.rs │ ├── generation │ ├── mod.rs │ └── spline.rs │ ├── lighting.rs │ ├── mod.rs │ ├── orphan.rs │ ├── persistence.rs │ └── registry.rs ├── resources ├── audio │ ├── blocks │ │ ├── 568422__sheyvan__friction-small-stone-1-1.wav │ │ ├── 568423__sheyvan__friction-small-stone-1-2.wav │ │ ├── 568424__sheyvan__impact-small-stone-1-1.wav │ │ ├── 569497__sheyvan__stone-impact-rubble-debris-1.wav │ │ ├── 569498__sheyvan__stone-impact-rubble-debris-2.wav │ │ ├── 569499__sheyvan__stone-impact-rubble-debris-3.wav │ │ ├── 569500__sheyvan__stone-impact-rubble-debris-4.wav │ │ ├── 569501__sheyvan__stone-impact-rubble-debris-5.wav │ │ ├── 569502__sheyvan__stone-impact-rubble-debris-6.wav │ │ ├── 569503__sheyvan__stone-impact-rubble-debris-7.wav │ │ ├── 569504__sheyvan__stone-impact-rubble-debris-8.wav │ │ ├── 569505__sheyvan__stone-impact-rubble-debris-9.wav │ │ ├── 569506__sheyvan__stone-impact-rubble-debris-10.wav │ │ ├── 569507__sheyvan__stone-impact-rubble-debris-11.wav │ │ ├── 569508__sheyvan__stone-impact-rubble-debris-12.wav │ │ ├── 569509__sheyvan__stone-impact-rubble-debris-13.wav │ │ ├── 569510__sheyvan__stone-impact-rubble-debris-14.wav │ │ ├── 569734__sheyvan__gravel-stone-dirt-debris-falling-small-1-1.wav │ │ ├── 569735__sheyvan__gravel-stone-dirt-debris-falling-small-1-2.wav │ │ ├── 569736__sheyvan__gravel-stone-dirt-debris-falling-small-1-3.wav │ │ ├── 569737__sheyvan__gravel-stone-dirt-debris-falling-small-1-4.wav │ │ ├── 569738__sheyvan__gravel-stone-dirt-debris-falling-small-1-5.wav │ │ ├── 569739__sheyvan__gravel-stone-dirt-debris-falling-small-1-6.wav │ │ ├── 569740__sheyvan__gravel-stone-dirt-debris-falling-small-1-7.wav │ │ ├── 569741__sheyvan__gravel-stone-dirt-debris-falling-small-1-8.wav │ │ ├── 569742__sheyvan__gravel-stone-dirt-debris-falling-small-1-9.wav │ │ ├── 569743__sheyvan__gravel-stone-dirt-debris-falling-small-1-10.wav │ │ ├── 569744__sheyvan__gravel-stone-dirt-debris-falling-small-1-11.wav │ │ ├── 569745__sheyvan__gravel-stone-dirt-debris-falling-small-1-12.wav │ │ ├── 569746__sheyvan__gravel-stone-dirt-debris-falling-small-1-13.wav │ │ ├── bassy-dirt-hit-1.wav │ │ ├── bassy-dirt-hit-2.wav │ │ ├── dirt-hit-1.wav │ │ ├── dirt-hit-2.wav │ │ ├── dirt-hit-3.wav │ │ ├── tall-grass-hit-1.wav │ │ ├── tall-grass-hit-2.wav │ │ ├── tall-grass-hit-3.wav │ │ ├── tall-grass-hit-4.wav │ │ ├── tall-grass-hit-5.wav │ │ └── tall-grass-hit-6.wav │ ├── credits.md │ ├── manifest.ron │ └── music │ │ └── ambient1.mp3 ├── blocks.json ├── shaders │ ├── adjustables.glsl │ ├── crosshair.glsl │ ├── debug.glsl │ ├── fullscreen_quad.vert │ ├── manifest.json │ ├── noise.glsl │ ├── post.glsl │ ├── sky.glsl │ ├── terrain │ │ ├── main.glsl │ │ ├── unpack.glsl │ │ └── wind.glsl │ └── util.glsl └── textures │ ├── blocks │ ├── detail_medium_grass.png │ ├── detail_short_grass.png │ ├── dirt.png │ ├── grass.png │ ├── grass_side.png │ ├── grass_variant_1.png │ ├── grass_variant_2.png │ ├── grass_variant_3.png │ ├── grass_variant_4.png │ ├── grass_variant_5.png │ ├── grass_variant_6.png │ ├── grass_variant_7.png │ ├── sand.png │ ├── stone.png │ ├── unknown.png │ └── water.png │ └── crosshair.png ├── rustfmt.toml └── source_textures └── blocks ├── detail_medium_grass.xcf ├── detail_short_grass.xcf ├── dirt.xcf ├── grass.xcf ├── grass_side.xcf ├── sand.xcf ├── stone.xcf ├── unknown.xcf └── water.xcf /.gitignore: -------------------------------------------------------------------------------- 1 | .vscode 2 | target 3 | 4 | save -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | resolver = "2" 3 | members = ["notcraft-client", "notcraft-common"] 4 | 5 | [profile.dev] 6 | opt-level = 1 7 | 8 | [profile.dev.package."*"] 9 | opt-level = 1 10 | -------------------------------------------------------------------------------- /IDEAS.md: -------------------------------------------------------------------------------- 1 | # async world 2 | 3 | **PROS**: 4 | - allows for straightforward, nonblocking world manipulation code 5 | ```rs 6 | async fn modify_world(world: &Arc, pos: BlockPos, block: BlockId) { 7 | let mut cache = ChunkCache::new(world); 8 | // might have to load or generate the chunk first, so async allows us to not have to handle that complexity every time we want to access the world. 9 | for dy in 0..10 { 10 | cache.set(pos.offset(Direction::Up, dy), block).await; 11 | } 12 | } 13 | 14 | async fn modify_world2(world: &Arc, pos: BlockPos, block: BlockId) { 15 | let mut patch = ChunkPatch::new(world); 16 | for dy in 0..10 { 17 | patch.set(pos.up(dy), block); 18 | } 19 | 20 | // might have to load or generate the chunk first, so async allows us to not have to handle that complexity every time we want to access the world. 21 | patch.apply().await; 22 | } 23 | ``` 24 | 25 | - *mutable world, mutable chunks*: 26 | - simplest approach 27 | - either one thread handles world access, or a lock must be held the entire time a chunk is being read/written. 28 | - *fully concurrent world*: 29 | - very complicated to implement world 30 | - likely not much better than just locking the world each time 31 | 32 | - *move chunks behind an `Arc`*: 33 | - still quite simple 34 | - r/w world access does not lock world for very long 35 | - concurrent w access + r/w access in the same chunk will block one of the threads until modification is complete 36 | - *move chunks behind an `Orphan`*: 37 | - still quite simple 38 | - r/w world access does not lock world for very long 39 | - concurrent r access is completely nonblocking, but may get slightly old world data 40 | - concurrent w access will only block other w accesses 41 | - *anything + sync chunk cache*: 42 | - nonblocking reads/writes for cache hits 43 | - nonblocking writes are always possible: chunk writes can be remembered, and applied later, when the target chunk is loaded, or perhaps with an explicit flush 44 | - missed chunk reads may have to block the calling thread entirely, until the chunks can be generated/loaded and cached. 45 | - *async world access*: 46 | - very simple, straighforward world modification code 47 | - completely nonblocking 48 | - *read-only access, write queue, sync writes at end of each tick*: 49 | - nonblocking reads are always possible 50 | - nonblocking writes are always possible 51 | - simple to implement 52 | - world is not updated immediately, may mor may not be a big issue -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Notcraft 2 | 3 | Notcraft is a sandboxy voxel game that I've been using to test out game programming stuff. I'm really not quite sure what I want to do with it? 4 | 5 | **This project is super barebones! And very buggy!** 6 | 7 | ## Controls 8 | 9 | Some controls listed here are subject to change in the future, as they are placeholders for yet unimplemented systems, including block picking and mouse grabbing. 10 | 11 | ### Movement 12 | - `W`: Move forwards 13 | - `S`: Move backwards 14 | - `A`: Move left 15 | - `D`: Move right 16 | - `Space`: Jump 17 | ### Miscellaneous 18 | - `Ctrl+C`: Toggle mouse grab 19 | - `Q`: Switch block used for placement 20 | - `Ctrl+shift+S`: Fix the camera in its current transform (easy to do by accident when using area select, can fix via the input below) 21 | - `Ctrl+shift+F`: Make the camera follow the player entity 22 | ### Terrain Manipulation 23 | - `E`: Destroy sphere of blocks 24 | - `Ctrl(Hold)`: Increase movement speed 25 | - `LeftClick`: Destroy one block 26 | - `RightClick`: Place one block 27 | - `Ctrl+RightClick`: Place line of blocks to player 28 | - `Ctrl+Shift+LeftClick`: Destroy area of blocks 29 | - `Ctrl+Shift+RightClick`: Place area of blocks 30 | 31 | ## Command Line Arguments 32 | 33 | - `--mesher-mode `: Changes whether the chunk mesher uses greedy meshing (combining similar block faces) or simple meshing (only does block face occlusion culling). currently, greedy meshing looks a bit strange due to randomized face textures, and is significantly slower 34 | - `-D, --enable-debug-events ...`: Debug events with names listed here will be toggled on, and may be processed by a debug event sink, like the one in `notcraft-client/src/client/debug.rs`. If the flag is specified, but no event names are given, then all debug events are enabled. The currently supported debug events are: 35 | - `world-load`: Chunk loading/unloading/modification events 36 | - `world-access`: Chunk reading/writing/orphaning events 37 | - `mesher`: Chunk meshing events 38 | 39 | # Hacking 40 | 41 | ## Static 42 | - Player movement and terrain manipulation code can be found in `notcraft-client/src/main.rs` (gotta clean that up lol) 43 | 44 | - The dynamic shader loader, along with the texture loader can be found in `notcraft-client/src/client/loader.rs` 45 | 46 | - The main renderer can be found in `notcraft-client/src/client/render/renderer.rs` 47 | 48 | - The chunk mesher, responsible for creating static terrain meshes out of chunk data can be found split across all the files in `notcraft-client/src/client/render/mesher` 49 | - `mod.rs` is the main driver, exposing a `ChunkMesherPlugin` that listens for chunk events and spins off meshing tasks 50 | - `tracker.rs` exposes `MeshTracker`, which keeps track of which chunks have enough loaded neighbor chunks to create chunk meshes for themselves. 51 | - `generation.rs` is the actual vertex buffer generation code 52 | 53 | - Terrain collision code can be found in `notcraft-common/src/physics.rs`, along with basic acceleration/velocity application code 54 | 55 | - Chunk generation code (i.e. the stuff responsible for actually sshaping the world) can be found in `notcraft-common/src/world/generation.rs` 56 | 57 | - Chunk management code and the main world struct can be found in `notcraft-common/src/world/mod.rs`, and chunk internals can be found in `notcraft-common/src/world/chunk.rs` 58 | 59 | ## Dynamic 60 | 61 | Dynamically-loaded resources are placed in `resources`, including audio files, textures, and shaders. Additionally, gameplay resources can be found there too, like `resources/blocks.json`, which describes a list of blocks and their properties to be loaded into the game upon startup. 62 | 63 | ## Shaders 64 | 65 | Notcraft includes a shader hot-reloading feature by default, as well as a crude preprocessor that allows for `#pragma include`-ing of other shader files. Saving a shader file while the game is running will cause itself and all dependants (via `#pragma include`) of itself to be recompiled and swapped in. 66 | 67 | - `resources/shaders/terrain` contains the shaders used to draw the voxel terrain 68 | 69 | - `resources/shaders/post.glsl` contains post-processing code, and is where fog is applied 70 | 71 | - `resources/shaders/sky.glsl` contains skybox drawing code, creating the sky gradient and the cloud layer 72 | 73 | - `resources/shaders/adjustables.glsl` contains a bunch of `#define`s for various constants and whatnot used int other shaders -------------------------------------------------------------------------------- /notcraft-client/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notcraft" 3 | version = "0.1.0" 4 | authors = ["Avi "] 5 | edition = "2021" 6 | license = "AGPL-3.0" 7 | 8 | [features] 9 | default = ["debug"] 10 | hot-reload = ["notify", "notcraft-common/hot-reload"] 11 | debug = ["hot-reload", "notcraft-common/debug"] 12 | 13 | [dependencies.notcraft-common] 14 | version = "0.1.0" 15 | path = "../notcraft-common" 16 | 17 | [dependencies] 18 | lazy_static = "1.3" 19 | log = "0.4" 20 | env_logger = "0.9.0" 21 | num-traits = "0.2" 22 | anyhow = "1.0.51" 23 | 24 | serde = "1.0" 25 | serde_derive = "1.0" 26 | serde_json = "1.0" 27 | ron = "0.7.0" 28 | 29 | flurry = "0.3.1" 30 | structopt = "0.3.25" 31 | noise = "0.7.0" 32 | rayon = "1.0.3" 33 | crossbeam-channel = "0.5" 34 | image = "0.21" 35 | ambisonic = "0.4.0" 36 | rand = "0.6" 37 | nalgebra = "0.29.0" 38 | approx = "0.5.0" 39 | glob = "0.3.0" 40 | 41 | glium = "0.30.2" 42 | bevy_core = "0.5.0" 43 | bevy_ecs = "0.5.0" 44 | bevy_app = "0.5.0" 45 | 46 | notify = { version = "5.0.0-pre.13", optional = true } 47 | 48 | # NOTE: the `send_guard` feature is important because we use raw rwlocks in chunk management code, 49 | # where it is very possible that locked chunks get send across thread boundaries, such that a raw 50 | # unlock happens on a different thread than the raw lock. 51 | parking_lot = { version = "0.11.2", features = ["send_guard"] } 52 | -------------------------------------------------------------------------------- /notcraft-client/src/audio_pool.rs: -------------------------------------------------------------------------------- 1 | use notcraft_common::prelude::*; 2 | use rand::Rng; 3 | use serde::Deserialize; 4 | use std::{collections::HashMap, fs::File, path::Path}; 5 | 6 | use crate::{ 7 | client::audio::{AudioId, AudioState, EmitterParameters}, 8 | WeightedList, 9 | }; 10 | 11 | // #[derive(Clone, Debug, PartialEq, Deserialize)] 12 | // pub struct ManifestPoolParams { 13 | // min_pitch: Option, 14 | // max_pitch: Option, 15 | // min_amplitude: Option, 16 | // max_amplitude: Option, 17 | // } 18 | 19 | #[derive(Clone, Debug, PartialEq, Deserialize)] 20 | pub enum ManifestNode { 21 | /// A reference to another node in the manifest 22 | Ref(String), 23 | Pool { 24 | inherit: Option, 25 | patterns: Option>, 26 | // #[serde(flatten)] 27 | // params: ManifestPoolParams, 28 | min_pitch: Option, 29 | max_pitch: Option, 30 | min_amplitude: Option, 31 | max_amplitude: Option, 32 | }, 33 | Choice(Vec>), 34 | Weighted(Vec<(usize, Box)>), 35 | Layered { 36 | /// node used when no sounds are selected to be played from the list of 37 | /// layers. 38 | default: Box, 39 | layers: Vec<(f32, Box)>, 40 | }, 41 | } 42 | 43 | #[derive(Debug)] 44 | enum ManifestError { 45 | Io(std::io::Error), 46 | Glob(glob::GlobError), 47 | UnknownReference(String), 48 | CannotInherit(String), 49 | } 50 | 51 | impl std::error::Error for ManifestError {} 52 | impl std::fmt::Display for ManifestError { 53 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 54 | match self { 55 | ManifestError::Io(err) => err.fmt(f), 56 | ManifestError::Glob(err) => err.fmt(f), 57 | ManifestError::UnknownReference(name) => { 58 | write!(f, "'{name}' was not found in the manifest") 59 | } 60 | ManifestError::CannotInherit(name) => write!(f, "cannot inherit from entry '{name}'"), 61 | } 62 | } 63 | } 64 | 65 | impl From for ManifestError { 66 | fn from(err: std::io::Error) -> Self { 67 | ManifestError::Io(err) 68 | } 69 | } 70 | 71 | impl From for ManifestError { 72 | fn from(err: glob::GlobError) -> Self { 73 | ManifestError::Glob(err) 74 | } 75 | } 76 | 77 | fn get_node_ref<'n>( 78 | name: &str, 79 | manifest: &'n AudioManifest, 80 | ) -> Result<&'n ManifestNode, ManifestError> { 81 | let res = manifest.0.get(name); 82 | res.ok_or_else(|| ManifestError::UnknownReference(name.into())) 83 | } 84 | 85 | struct InheritanceOut<'n> { 86 | patterns: &'n Option>, 87 | min_pitch: &'n Option, 88 | max_pitch: &'n Option, 89 | min_amplitude: &'n Option, 90 | max_amplitude: &'n Option, 91 | } 92 | 93 | fn resolve_inheritance<'n>( 94 | node: &'n ManifestNode, 95 | manifest: &'n AudioManifest, 96 | name: &str, 97 | out: &mut InheritanceOut<'n>, 98 | ) -> Result<(), ManifestError> { 99 | match node { 100 | ManifestNode::Ref(name) => { 101 | resolve_inheritance(get_node_ref(name, manifest)?, manifest, name, out)? 102 | } 103 | ManifestNode::Pool { 104 | inherit, 105 | patterns, 106 | min_pitch, 107 | max_pitch, 108 | min_amplitude, 109 | max_amplitude, 110 | } => { 111 | fn apply<'a, T>(out: &mut &'a Option, val: &'a Option) { 112 | if out.is_none() { 113 | *out = val; 114 | } 115 | } 116 | 117 | apply(&mut out.patterns, patterns); 118 | apply(&mut out.min_pitch, &min_pitch); 119 | apply(&mut out.max_pitch, &max_pitch); 120 | apply(&mut out.min_amplitude, &min_amplitude); 121 | apply(&mut out.max_amplitude, &max_amplitude); 122 | 123 | if let Some(name) = inherit { 124 | resolve_inheritance(get_node_ref(name, manifest)?, manifest, name, out)? 125 | } 126 | } 127 | _ => return Err(ManifestError::CannotInherit(name.into())), 128 | } 129 | Ok(()) 130 | } 131 | 132 | fn resolve_node( 133 | node: &ManifestNode, 134 | manifest: &AudioManifest, 135 | state: &mut AudioState, 136 | last_name: &str, 137 | ) -> Result { 138 | Ok(match node { 139 | ManifestNode::Ref(name) => { 140 | resolve_node(get_node_ref(name, manifest)?, manifest, state, name)? 141 | } 142 | ManifestNode::Pool { 143 | inherit: _, 144 | patterns, 145 | min_pitch, 146 | max_pitch, 147 | min_amplitude, 148 | max_amplitude, 149 | } => { 150 | let mut out = InheritanceOut { 151 | patterns, 152 | min_pitch, 153 | max_pitch, 154 | min_amplitude, 155 | max_amplitude, 156 | }; 157 | resolve_inheritance(node, manifest, last_name, &mut out)?; 158 | 159 | let empty = Vec::new(); 160 | let patterns = out.patterns.as_ref().unwrap_or(&empty); 161 | let params = EmitterParameters { 162 | min_pitch: out.min_pitch.unwrap_or(1.0), 163 | max_pitch: out.max_pitch.unwrap_or(1.0), 164 | min_amplitude: out.min_amplitude.unwrap_or(1.0), 165 | max_amplitude: out.max_amplitude.unwrap_or(1.0), 166 | }; 167 | 168 | let mut items = WeightedList::default(); 169 | for pattern in patterns.iter() { 170 | // TODO: does this allow attackers to use `..` to escape the resources dir? 171 | // would it even matter? 172 | for path in glob::glob(&format!("resources/audio/{pattern}"))? { 173 | let id = state.add(File::open(path?)?)?; 174 | items.push(1, Box::new(AudioNode::Sound { id, params })); 175 | } 176 | } 177 | AudioNode::Choice(items) 178 | } 179 | 180 | ManifestNode::Choice(choices) => { 181 | let mut items = WeightedList::default(); 182 | for choice in choices.iter() { 183 | let node = resolve_node(choice, manifest, state, last_name)?; 184 | items.push(1, Box::new(node)); 185 | } 186 | AudioNode::Choice(items) 187 | } 188 | ManifestNode::Weighted(choices) => { 189 | let mut items = WeightedList::default(); 190 | for &(weight, ref choice) in choices.iter() { 191 | let node = resolve_node(choice, manifest, state, last_name)?; 192 | items.push(weight, Box::new(node)); 193 | } 194 | AudioNode::Choice(items) 195 | } 196 | ManifestNode::Layered { default, layers } => AudioNode::Layered { 197 | default: Box::new(resolve_node(default, manifest, state, last_name)?), 198 | layers: { 199 | let mut out = Vec::with_capacity(layers.len()); 200 | for &(probability, ref layer) in layers.iter() { 201 | let node = resolve_node(layer, manifest, state, last_name)?; 202 | out.push((probability, Box::new(node))); 203 | } 204 | out 205 | }, 206 | }, 207 | }) 208 | } 209 | 210 | #[derive(Clone, Debug, PartialEq, Deserialize)] 211 | pub struct AudioManifest(HashMap); 212 | 213 | #[derive(Clone, Debug)] 214 | enum AudioNode { 215 | Sound { 216 | id: AudioId, 217 | params: EmitterParameters, 218 | }, 219 | Layered { 220 | default: Box, 221 | layers: Vec<(f32, Box)>, 222 | }, 223 | Choice(WeightedList>), 224 | } 225 | 226 | #[derive(Clone, Debug, Default)] 227 | pub struct RandomizedAudioPools { 228 | sound_idx_map: HashMap, 229 | sounds: Vec, 230 | } 231 | 232 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 233 | pub struct SoundId(usize); 234 | 235 | impl RandomizedAudioPools { 236 | pub fn id(&self, name: &str) -> Option { 237 | self.sound_idx_map.get(name).copied().map(SoundId) 238 | } 239 | 240 | pub fn select(&self, rng: &mut R, id: SoundId, mut func: F) 241 | where 242 | R: Rng + ?Sized, 243 | F: FnMut(AudioId, EmitterParameters), 244 | { 245 | select_sounds(rng, &self.sounds[id.0], &mut func) 246 | } 247 | } 248 | 249 | fn select_sounds(rng: &mut R, node: &AudioNode, func: &mut F) 250 | where 251 | R: Rng + ?Sized, 252 | F: FnMut(AudioId, EmitterParameters), 253 | { 254 | match node { 255 | &AudioNode::Sound { id, params } => func(id, params), 256 | AudioNode::Layered { default, layers } => { 257 | let mut use_default = true; 258 | for &(probability, ref node) in layers.iter() { 259 | if rng.gen_bool(probability as f64) { 260 | use_default = false; 261 | select_sounds(rng, node, func) 262 | } 263 | } 264 | if use_default { 265 | select_sounds(rng, default, func) 266 | } 267 | } 268 | AudioNode::Choice(choices) => { 269 | if let Some(choice) = choices.select(rng) { 270 | select_sounds(rng, choice, func) 271 | } 272 | } 273 | } 274 | } 275 | 276 | pub fn load_audio>(path: P, state: &mut AudioState) -> Result { 277 | let manifest: AudioManifest = ron::from_str(&std::fs::read_to_string(path)?)?; 278 | 279 | let mut pools = RandomizedAudioPools::default(); 280 | for (name, node) in manifest.0.iter() { 281 | match resolve_node(node, &manifest, state, name) { 282 | Ok(resolved) => { 283 | pools.sound_idx_map.insert(name.into(), pools.sounds.len()); 284 | pools.sounds.push(resolved); 285 | } 286 | // having a screwed up entry is ok, we just don't add it to the pools. 287 | Err(err) => log::error!("audio manifest entry '{name}' failed to load: {err}"), 288 | } 289 | } 290 | 291 | Ok(pools) 292 | } 293 | -------------------------------------------------------------------------------- /notcraft-client/src/client/audio.rs: -------------------------------------------------------------------------------- 1 | use ambisonic::{ 2 | rodio::{Decoder, Source}, 3 | Ambisonic, AmbisonicBuilder, SoundController, 4 | }; 5 | use nalgebra::{Point3, SimdComplexField, Vector3}; 6 | use notcraft_common::{prelude::*, transform::Transform}; 7 | use num_traits::Pow; 8 | use rand::distributions::{Distribution, Uniform}; 9 | use std::{ 10 | collections::HashMap, 11 | io::{Cursor, Read}, 12 | sync::Arc, 13 | time::{Duration, Instant}, 14 | }; 15 | 16 | pub struct AudioState { 17 | next_id: AudioId, 18 | audio: HashMap>, 19 | } 20 | 21 | impl AudioState { 22 | pub fn new() -> Result { 23 | Ok(Self { 24 | next_id: AudioId(0), 25 | audio: Default::default(), 26 | }) 27 | } 28 | 29 | pub fn add(&mut self, mut reader: R) -> Result 30 | where 31 | R: Read, 32 | { 33 | let mut data = Vec::new(); 34 | reader.read_to_end(&mut data)?; 35 | 36 | let id = self.next_id; 37 | self.next_id.0 += 1; 38 | 39 | self.audio.insert(id, data.into_boxed_slice().into()); 40 | Ok(id) 41 | } 42 | 43 | fn get(&self, id: AudioId) -> Arc<[u8]> { 44 | self.audio[&id].clone() 45 | } 46 | } 47 | 48 | #[derive(Clone, Debug)] 49 | pub struct AudioListener { 50 | // TODO: make this work. or maybe we want a different api? 51 | volume: f32, 52 | } 53 | 54 | impl Default for AudioListener { 55 | fn default() -> Self { 56 | Self { volume: 1.0 } 57 | } 58 | } 59 | 60 | // might add custom sources in the future 61 | #[derive(Debug)] 62 | pub enum EmitterSource { 63 | Sample(AudioId), 64 | } 65 | 66 | #[derive(Copy, Clone, Debug)] 67 | pub struct EmitterParameters { 68 | pub min_pitch: f32, 69 | pub max_pitch: f32, 70 | pub min_amplitude: f32, 71 | pub max_amplitude: f32, 72 | } 73 | 74 | impl Default for EmitterParameters { 75 | fn default() -> Self { 76 | Self { 77 | min_pitch: 1.0, 78 | max_pitch: 1.0, 79 | min_amplitude: 1.0, 80 | max_amplitude: 1.0, 81 | } 82 | } 83 | } 84 | 85 | pub struct AudioEmitter { 86 | sound: SoundController, 87 | start: Instant, 88 | duration: Option, 89 | } 90 | 91 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] 92 | struct DespawnEmitter; 93 | 94 | impl std::fmt::Debug for AudioEmitter { 95 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 96 | f.debug_struct("AudioEmitter") 97 | .field("start", &self.start) 98 | .field("duration", &self.duration) 99 | .finish_non_exhaustive() 100 | } 101 | } 102 | 103 | fn update_emitters( 104 | mut cmd: Commands, 105 | active_listener: Res, 106 | listener_query: Query<(&Transform, &AudioListener)>, 107 | emitter_query: Query<( 108 | Entity, 109 | &Transform, 110 | &mut AudioEmitter, 111 | Option<&DespawnEmitter>, 112 | )>, 113 | ) { 114 | let (listener_transform, _) = match active_listener.0.and_then(|e| listener_query.get(e).ok()) { 115 | Some(it) => it, 116 | _ => return, 117 | }; 118 | 119 | emitter_query.for_each_mut(|(entity, transform, mut emitter, despawn)| { 120 | match emitter.duration { 121 | Some(duration) if emitter.start.elapsed() > duration => match despawn { 122 | Some(DespawnEmitter) => cmd.entity(entity).despawn(), 123 | None => drop(cmd.entity(entity).remove::()), 124 | }, 125 | 126 | _ => { 127 | // TODO: we could also update emitter velocities here! 128 | let matrix = listener_transform.to_matrix().try_inverse().unwrap(); 129 | let audio_pos = matrix.transform_point(&transform.pos()); 130 | 131 | emitter.sound.adjust_position(audio_pos.into()); 132 | } 133 | } 134 | }); 135 | } 136 | 137 | fn curve_audio_amplitude(distance: f32) -> f32 { 138 | const NEAR_EXP: f32 = 0.85; 139 | const FAR_EXP: f32 = 0.5; 140 | const CUTOFF: f32 = 7.0; 141 | 142 | if distance <= CUTOFF { 143 | distance.pow(NEAR_EXP) 144 | } else { 145 | distance.pow(FAR_EXP) - CUTOFF.pow(FAR_EXP) + CUTOFF.pow(NEAR_EXP) 146 | } 147 | } 148 | 149 | // fn make_spatial_source() -> impl ambisonic::rodio::Source {} 150 | 151 | fn process_audio_events( 152 | mut cmd: Commands, 153 | audio_scene: NonSend, 154 | state: Res, 155 | mut events: EventReader, 156 | active_listener: Res, 157 | listener_query: Query<(&Transform, &AudioListener)>, 158 | emitter_query: Query<(Entity, &Transform)>, 159 | ) { 160 | let (listener_transform, _) = match active_listener.0.and_then(|e| listener_query.get(e).ok()) { 161 | Some(it) => it, 162 | _ => return, 163 | }; 164 | 165 | let mut rng = rand::thread_rng(); 166 | for event in events.iter() { 167 | let source = match &event.source().source { 168 | &EmitterSource::Sample(id) => Decoder::new(Cursor::new(state.get(id))), 169 | }; 170 | let params = &event.source().params; 171 | let speed = Uniform::new_inclusive(params.min_pitch, params.max_pitch).sample(&mut rng); 172 | let amplitude = 173 | Uniform::new_inclusive(params.min_amplitude, params.max_amplitude).sample(&mut rng); 174 | // TODO: unwrap 175 | let source = source 176 | .unwrap() 177 | .convert_samples() 178 | .speed(speed) 179 | .amplify(amplitude); 180 | match event { 181 | AudioEvent::PlaySpatial(entity, _) => { 182 | if let Ok((entity, transform)) = emitter_query.get(*entity) { 183 | let duration = source.total_duration(); 184 | 185 | let matrix = listener_transform.to_matrix().try_inverse().unwrap(); 186 | let audio_pos = matrix.transform_point(&transform.pos()); 187 | 188 | // TODO: curving amplitude via `.amplify()` mostly works, though the amplitude 189 | // is not modified when the listener moves, so initially-distant long-running 190 | // sounds could get really loud if the listener moves close to it. 191 | let sound = audio_scene.play_at( 192 | source.amplify(curve_audio_amplitude(audio_pos.coords.magnitude())), 193 | audio_pos.into(), 194 | ); 195 | cmd.entity(entity).insert(AudioEmitter { 196 | sound, 197 | start: Instant::now(), 198 | duration, 199 | }); 200 | } 201 | } 202 | 203 | &AudioEvent::SpawnSpatial(pos, _) => { 204 | let duration = source.total_duration(); 205 | 206 | let matrix = listener_transform.to_matrix().try_inverse().unwrap(); 207 | let audio_pos = matrix.transform_point(&pos); 208 | 209 | let sound = audio_scene.play_at( 210 | source.amplify(curve_audio_amplitude(audio_pos.coords.magnitude())), 211 | audio_pos.into(), 212 | ); 213 | cmd.spawn() 214 | .insert(Transform::to(pos)) 215 | .insert(DespawnEmitter) 216 | .insert(AudioEmitter { 217 | sound, 218 | start: Instant::now(), 219 | duration, 220 | }); 221 | } 222 | 223 | AudioEvent::PlayGlobal(_) => { 224 | // TODO: unwrap 225 | audio_scene.play_omni(source); 226 | } 227 | } 228 | } 229 | } 230 | 231 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 232 | pub struct AudioId(usize); 233 | 234 | #[derive(Debug)] 235 | pub struct ParameterizedSource { 236 | pub source: EmitterSource, 237 | pub params: EmitterParameters, 238 | } 239 | 240 | impl ParameterizedSource { 241 | pub fn from_sample(id: AudioId) -> Self { 242 | Self { 243 | source: EmitterSource::Sample(id), 244 | params: Default::default(), 245 | } 246 | } 247 | 248 | pub fn with_parameters(mut self, value: EmitterParameters) -> Self { 249 | self.params = value; 250 | self 251 | } 252 | 253 | pub fn with_min_pitch(mut self, value: f32) -> Self { 254 | self.params.min_pitch = value; 255 | self 256 | } 257 | 258 | pub fn with_max_pitch(mut self, value: f32) -> Self { 259 | self.params.max_pitch = value; 260 | self 261 | } 262 | 263 | pub fn with_pitch(mut self, value: f32) -> Self { 264 | self.params.min_pitch = value; 265 | self.params.max_pitch = value; 266 | self 267 | } 268 | 269 | pub fn with_min_amplitude(mut self, value: f32) -> Self { 270 | self.params.min_amplitude = value; 271 | self 272 | } 273 | 274 | pub fn with_max_amplitude(mut self, value: f32) -> Self { 275 | self.params.max_amplitude = value; 276 | self 277 | } 278 | 279 | pub fn with_amplitude(mut self, value: f32) -> Self { 280 | self.params.min_amplitude = value; 281 | self.params.max_amplitude = value; 282 | self 283 | } 284 | } 285 | 286 | #[derive(Debug)] 287 | pub enum AudioEvent { 288 | /// Notifies the sound system to play a 3D sound at the given entity's 289 | /// location, and attaches an [`AudioEmitter`] component to the entity. 290 | /// If the entity is moved, the audio emitter will be as well. The component 291 | /// will be removed from the entity when the sound is done playing. 292 | PlaySpatial(Entity, ParameterizedSource), 293 | 294 | /// Similar to [`AudioEvent::PlaySpatial`], except this also spawns a 295 | /// temporary "holder" object to contain the emitter. The temporary 296 | /// entity is managed by the audio system and is despawned after the 297 | /// sound is done playing. 298 | SpawnSpatial(Point3, ParameterizedSource), 299 | 300 | /// Notifies the sound system to play a sound directly to the active audio 301 | /// listener, without spatial effects. 302 | PlayGlobal(ParameterizedSource), 303 | } 304 | 305 | impl AudioEvent { 306 | pub fn source(&self) -> &ParameterizedSource { 307 | match self { 308 | AudioEvent::PlaySpatial(_, source) 309 | | AudioEvent::SpawnSpatial(_, source) 310 | | AudioEvent::PlayGlobal(source) => source, 311 | } 312 | } 313 | } 314 | 315 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 316 | pub struct ActiveAudioListener(pub Option); 317 | 318 | #[derive(Debug, Default)] 319 | pub struct AudioPlugin {} 320 | 321 | impl Plugin for AudioPlugin { 322 | fn build(&self, app: &mut AppBuilder) { 323 | app.insert_non_send_resource(AmbisonicBuilder::default().build()); 324 | app.insert_resource(AudioState::new().expect("failed to init audio")); 325 | app.insert_resource(ActiveAudioListener(None)); 326 | 327 | app.add_event::(); 328 | 329 | app.add_system_to_stage(CoreStage::PostUpdate, update_emitters.system()); 330 | app.add_system_to_stage(CoreStage::PostUpdate, process_audio_events.system()); 331 | } 332 | } 333 | -------------------------------------------------------------------------------- /notcraft-client/src/client/camera.rs: -------------------------------------------------------------------------------- 1 | use bevy_ecs::system::SystemParam; 2 | use nalgebra::{Matrix4, Perspective3, Point3}; 3 | use notcraft_common::{prelude::*, transform::Transform}; 4 | 5 | #[derive(Copy, Clone, Debug, PartialEq)] 6 | pub struct Camera { 7 | pub projection: Perspective3, 8 | } 9 | 10 | #[derive(Copy, Clone, Debug, PartialEq, Default)] 11 | pub struct ActiveCamera(pub Option); 12 | 13 | impl Camera { 14 | pub fn projection_matrix(&self) -> Matrix4 { 15 | self.projection.into() 16 | } 17 | } 18 | 19 | impl Default for Camera { 20 | fn default() -> Self { 21 | Camera { 22 | projection: Perspective3::new(1.0, std::f32::consts::PI / 2.0, 0.1, 1000.0), 23 | } 24 | } 25 | } 26 | 27 | #[derive(SystemParam)] 28 | pub struct CurrentCamera<'a> { 29 | active: Res<'a, ActiveCamera>, 30 | query: Query<'a, (&'static Camera, &'static Transform)>, 31 | } 32 | 33 | impl<'a> CurrentCamera<'a> { 34 | pub fn pos(&self) -> Point3 { 35 | self.active 36 | .0 37 | .and_then(|active| self.query.get(active).ok()) 38 | .map(|(_, transform)| Point3::from(transform.translation.vector)) 39 | .unwrap_or(point![0.0, 0.0, 0.0]) 40 | } 41 | 42 | pub fn view(&self) -> Matrix4 { 43 | self.active 44 | .0 45 | .and_then(|active| self.query.get(active).ok()) 46 | .and_then(|(_, transform)| transform.to_matrix().try_inverse()) 47 | .unwrap_or_else(|| Matrix4::identity()) 48 | } 49 | 50 | pub fn projection(&self, (width, height): (u32, u32)) -> Perspective3 { 51 | let mut proj = self 52 | .active 53 | .0 54 | .and_then(|active| self.query.get(active).ok()) 55 | .map(|(camera, _)| camera.projection) 56 | .unwrap_or_else(|| Camera::default().projection); 57 | proj.set_aspect(width as f32 / height as f32); 58 | proj 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /notcraft-client/src/client/debug.rs: -------------------------------------------------------------------------------- 1 | use std::time::Duration; 2 | 3 | use notcraft_common::{ 4 | aabb::Aabb, 5 | debug::drain_debug_events, 6 | debug_events, 7 | world::{ 8 | chunk::{ChunkSectionPos, CHUNK_LENGTH}, 9 | chunk_section_aabb, 10 | debug::{WorldAccessEvent, WorldLoadEvent}, 11 | ChunkPos, 12 | }, 13 | }; 14 | 15 | use super::render::renderer::{add_debug_box, add_transient_debug_box, DebugBox, DebugBoxKind}; 16 | 17 | pub enum MesherEvent { 18 | Meshed { cheap: bool, pos: ChunkSectionPos }, 19 | MeshFailed(ChunkSectionPos), 20 | } 21 | 22 | debug_events! { 23 | events, 24 | MesherEvent => "mesher", 25 | } 26 | 27 | pub fn debug_chunk_aabb(pos: ChunkPos) -> Aabb { 28 | let len = CHUNK_LENGTH as f32; 29 | let min = len * nalgebra::point![pos.x as f32, 24.0, pos.z as f32]; 30 | let max = min + len * nalgebra::vector![1.0, 0.0, 1.0]; 31 | Aabb { min, max } 32 | } 33 | 34 | // TODO: make the debug line renderer just a more generic line renderer and 35 | // require it as a resource here. 36 | pub fn debug_event_handler() { 37 | drain_debug_events::(|event| match event { 38 | WorldLoadEvent::Loaded(pos) => add_transient_debug_box( 39 | Duration::from_secs(1), 40 | DebugBox::new(debug_chunk_aabb(pos)) 41 | .with_color([0.0, 1.0, 0.0, 0.8]) 42 | .with_kind(DebugBoxKind::Solid), 43 | ), 44 | WorldLoadEvent::Unloaded(pos) => add_transient_debug_box( 45 | Duration::from_secs(1), 46 | DebugBox::new(debug_chunk_aabb(pos)) 47 | .with_color([1.0, 0.0, 0.0, 0.8]) 48 | .with_kind(DebugBoxKind::Solid), 49 | ), 50 | WorldLoadEvent::Modified(pos) => add_transient_debug_box( 51 | Duration::from_secs_f32(0.5), 52 | DebugBox::new(debug_chunk_aabb(pos)) 53 | .with_color([1.0, 1.0, 0.0, 0.3]) 54 | .with_kind(DebugBoxKind::Dashed), 55 | ), 56 | WorldLoadEvent::LoadedSection(pos) => add_transient_debug_box( 57 | Duration::from_secs(1), 58 | DebugBox::new(chunk_section_aabb(pos)) 59 | .with_color([0.0, 1.0, 0.0, 0.8]) 60 | .with_kind(DebugBoxKind::Solid), 61 | ), 62 | WorldLoadEvent::UnloadedSection(pos) => add_transient_debug_box( 63 | Duration::from_secs(1), 64 | DebugBox::new(chunk_section_aabb(pos)) 65 | .with_color([1.0, 0.0, 0.0, 0.8]) 66 | .with_kind(DebugBoxKind::Solid), 67 | ), 68 | WorldLoadEvent::ModifiedSection(pos) => add_transient_debug_box( 69 | Duration::from_secs_f32(0.5), 70 | DebugBox::new(chunk_section_aabb(pos)) 71 | .with_color([1.0, 1.0, 0.0, 0.3]) 72 | .with_kind(DebugBoxKind::Dashed), 73 | ), 74 | }); 75 | 76 | drain_debug_events::(|event| match event { 77 | WorldAccessEvent::Read(pos) => add_debug_box( 78 | DebugBox::new(chunk_section_aabb(pos)) 79 | .with_color([0.4, 0.4, 1.0, 0.1]) 80 | .with_kind(DebugBoxKind::Dotted), 81 | ), 82 | WorldAccessEvent::Written(pos) => add_debug_box( 83 | DebugBox::new(chunk_section_aabb(pos)) 84 | .with_color([1.0, 0.8, 0.4, 0.1]) 85 | .with_kind(DebugBoxKind::Dotted), 86 | ), 87 | WorldAccessEvent::Orphaned(pos) => add_transient_debug_box( 88 | Duration::from_secs(2), 89 | DebugBox::new(chunk_section_aabb(pos)) 90 | .with_color([1.0, 0.0, 0.0, 1.0]) 91 | .with_kind(DebugBoxKind::Solid), 92 | ), 93 | }); 94 | 95 | drain_debug_events::(|event| match event { 96 | MesherEvent::Meshed { cheap: true, pos } => add_transient_debug_box( 97 | Duration::from_secs(1), 98 | DebugBox::new(chunk_section_aabb(pos)) 99 | .with_color([1.0, 0.0, 1.0, 0.3]) 100 | .with_kind(DebugBoxKind::Dashed), 101 | ), 102 | MesherEvent::Meshed { cheap: false, pos } => add_transient_debug_box( 103 | Duration::from_secs(1), 104 | DebugBox::new(chunk_section_aabb(pos)) 105 | .with_color([1.0, 1.0, 0.0, 0.3]) 106 | .with_kind(DebugBoxKind::Dashed), 107 | ), 108 | MesherEvent::MeshFailed(pos) => add_transient_debug_box( 109 | Duration::from_secs(2), 110 | DebugBox::new(chunk_section_aabb(pos)) 111 | .with_color([1.0, 0.0, 0.0, 1.0]) 112 | .with_kind(DebugBoxKind::Solid), 113 | ), 114 | }); 115 | } 116 | -------------------------------------------------------------------------------- /notcraft-client/src/client/input.rs: -------------------------------------------------------------------------------- 1 | use glium::{ 2 | glutin::{ 3 | event::{ 4 | ButtonId, DeviceEvent, DeviceId, ElementState, KeyboardInput, ModifiersState, 5 | MouseScrollDelta, VirtualKeyCode, WindowEvent, 6 | }, 7 | window::{Window, WindowId}, 8 | }, 9 | Display, 10 | }; 11 | use notcraft_common::prelude::*; 12 | use std::{ 13 | collections::{HashMap, HashSet}, 14 | rc::Rc, 15 | sync::atomic::{AtomicBool, Ordering}, 16 | }; 17 | 18 | // digital as in "on or off" 19 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 20 | pub enum DigitalInput { 21 | Physical(u32), 22 | Button(u32), 23 | Virtual(VirtualKeyCode), 24 | } 25 | 26 | impl From for DigitalInput { 27 | fn from(vkk: VirtualKeyCode) -> Self { 28 | DigitalInput::Virtual(vkk) 29 | } 30 | } 31 | 32 | impl From for DigitalInput { 33 | fn from(sc: u32) -> Self { 34 | DigitalInput::Physical(sc) 35 | } 36 | } 37 | 38 | #[derive(Debug)] 39 | pub struct InputState { 40 | physical_map: HashMap, 41 | 42 | rising_keys: HashSet, 43 | falling_keys: HashSet, 44 | pressed_keys: HashSet, 45 | 46 | rising_buttons: HashSet, 47 | falling_buttons: HashSet, 48 | pressed_buttons: HashSet, 49 | 50 | current_modifiers: ModifiersState, 51 | 52 | cursor_dx: f32, 53 | cursor_dy: f32, 54 | pub sensitivity: f32, 55 | 56 | cursor_currently_grabbed: bool, 57 | cursor_should_be_grabbed: AtomicBool, 58 | cursor_currently_hidden: bool, 59 | cursor_should_be_hidden: AtomicBool, 60 | 61 | focused: bool, 62 | } 63 | 64 | impl Default for InputState { 65 | fn default() -> Self { 66 | InputState { 67 | physical_map: Default::default(), 68 | rising_keys: Default::default(), 69 | falling_keys: Default::default(), 70 | pressed_keys: Default::default(), 71 | rising_buttons: Default::default(), 72 | falling_buttons: Default::default(), 73 | pressed_buttons: Default::default(), 74 | 75 | current_modifiers: Default::default(), 76 | 77 | cursor_dx: 0.0, 78 | cursor_dy: 0.0, 79 | sensitivity: 0.10, 80 | 81 | cursor_currently_grabbed: false, 82 | cursor_should_be_grabbed: false.into(), 83 | cursor_currently_hidden: false, 84 | cursor_should_be_hidden: false.into(), 85 | 86 | focused: true, 87 | } 88 | } 89 | } 90 | 91 | impl InputState { 92 | pub fn grab_cursor(&self, grab: bool) { 93 | self.cursor_should_be_grabbed.store(grab, Ordering::SeqCst); 94 | } 95 | 96 | pub fn hide_cursor(&self, hide: bool) { 97 | self.cursor_should_be_hidden.store(hide, Ordering::SeqCst); 98 | } 99 | 100 | pub fn is_cursor_grabbed(&self) -> bool { 101 | self.cursor_should_be_grabbed.load(Ordering::SeqCst) 102 | } 103 | 104 | pub fn is_cursor_hidden(&self) -> bool { 105 | self.cursor_should_be_hidden.load(Ordering::SeqCst) 106 | } 107 | 108 | pub fn cursor_delta(&self) -> nalgebra::Vector2 { 109 | self.sensitivity * nalgebra::vector![self.cursor_dx, self.cursor_dy] 110 | } 111 | 112 | pub fn key>(&self, key: K) -> KeyRef { 113 | KeyRef { 114 | state: self, 115 | key: key.into(), 116 | modifiers_to_match: None, 117 | } 118 | } 119 | 120 | pub fn ctrl(&self) -> bool { 121 | self.current_modifiers.ctrl() 122 | } 123 | 124 | pub fn alt(&self) -> bool { 125 | self.current_modifiers.alt() 126 | } 127 | 128 | pub fn shift(&self) -> bool { 129 | self.current_modifiers.shift() 130 | } 131 | 132 | pub fn logo(&self) -> bool { 133 | self.current_modifiers.logo() 134 | } 135 | 136 | fn modifiers_match(&self, modifiers: Option) -> bool { 137 | modifiers.map_or(true, |modifiers| self.current_modifiers == modifiers) 138 | } 139 | 140 | fn is_key_in_set<'s>( 141 | &'s self, 142 | key: DigitalInput, 143 | key_set: &'s HashSet, 144 | button_set: &'s HashSet, 145 | ) -> Option { 146 | Some(match key { 147 | DigitalInput::Button(id) => button_set.contains(&id), 148 | DigitalInput::Virtual(vkk) => key_set.contains(self.physical_map.get(&vkk)?), 149 | DigitalInput::Physical(code) => key_set.contains(&code), 150 | }) 151 | } 152 | } 153 | 154 | pub struct KeyRef<'s> { 155 | state: &'s InputState, 156 | key: DigitalInput, 157 | modifiers_to_match: Option, 158 | } 159 | 160 | impl<'s> KeyRef<'s> { 161 | pub fn require_modifiers(self, modifiers: ModifiersState) -> Self { 162 | Self { 163 | modifiers_to_match: Some(modifiers), 164 | ..self 165 | } 166 | } 167 | 168 | pub fn is_pressed(&self) -> bool { 169 | let key = self 170 | .state 171 | .is_key_in_set( 172 | self.key, 173 | &self.state.pressed_keys, 174 | &self.state.pressed_buttons, 175 | ) 176 | .unwrap_or(false); 177 | key && self.state.modifiers_match(self.modifiers_to_match) 178 | } 179 | 180 | pub fn is_rising(&self) -> bool { 181 | let key = self 182 | .state 183 | .is_key_in_set( 184 | self.key, 185 | &self.state.rising_keys, 186 | &self.state.rising_buttons, 187 | ) 188 | .unwrap_or(false); 189 | key && self.state.modifiers_match(self.modifiers_to_match) 190 | } 191 | 192 | pub fn is_falling(&self) -> bool { 193 | let key = self 194 | .state 195 | .is_key_in_set( 196 | self.key, 197 | &self.state.falling_keys, 198 | &self.state.falling_buttons, 199 | ) 200 | .unwrap_or(false); 201 | key && self.state.modifiers_match(self.modifiers_to_match) 202 | } 203 | } 204 | 205 | pub mod keys { 206 | use glium::glutin::event::VirtualKeyCode; 207 | 208 | pub const FORWARD: u32 = 0x11; 209 | pub const BACKWARD: u32 = 0x1F; 210 | pub const LEFT: u32 = 0x1E; 211 | pub const RIGHT: u32 = 0x20; 212 | pub const UP: u32 = 0x39; 213 | pub const DOWN: u32 = 0x2A; 214 | 215 | pub const ARROW_UP: VirtualKeyCode = VirtualKeyCode::Up; 216 | pub const ARROW_DOWN: VirtualKeyCode = VirtualKeyCode::Down; 217 | pub const ARROW_LEFT: VirtualKeyCode = VirtualKeyCode::Left; 218 | pub const ARROW_RIGHT: VirtualKeyCode = VirtualKeyCode::Right; 219 | } 220 | 221 | fn maintain_input_state(state: &mut InputState, window: &Window) {} 222 | 223 | fn notify_keyboard_input(state: &mut InputState, input: KeyboardInput) { 224 | // update tracked modifier state 225 | let to_set = match input.virtual_keycode { 226 | Some(VirtualKeyCode::LShift) | Some(VirtualKeyCode::RShift) => ModifiersState::SHIFT, 227 | Some(VirtualKeyCode::LAlt) | Some(VirtualKeyCode::RAlt) => ModifiersState::ALT, 228 | Some(VirtualKeyCode::LControl) | Some(VirtualKeyCode::RControl) => ModifiersState::CTRL, 229 | Some(VirtualKeyCode::LWin) | Some(VirtualKeyCode::RWin) => ModifiersState::LOGO, 230 | _ => ModifiersState::empty(), 231 | }; 232 | 233 | let pressed = matches!(input.state, ElementState::Pressed); 234 | state.current_modifiers.set(to_set, pressed); 235 | 236 | // add virtual keycode -> scancode mapping 237 | if let Some(vkk) = input.virtual_keycode { 238 | if state.physical_map.insert(vkk, input.scancode).is_none() { 239 | log::debug!("found physical mapping for '{:?}': {}", vkk, input.scancode); 240 | } 241 | } 242 | 243 | // update rising/falling sets 244 | if pressed && state.pressed_keys.insert(input.scancode) { 245 | state.rising_keys.insert(input.scancode); 246 | } else if !pressed && state.pressed_keys.remove(&input.scancode) { 247 | state.falling_keys.insert(input.scancode); 248 | } 249 | } 250 | 251 | fn notify_mouse_motion(state: &mut InputState, dx: f64, dy: f64) { 252 | state.cursor_dx += dx as f32; 253 | state.cursor_dy += dy as f32; 254 | } 255 | 256 | fn notify_mouse_scroll(_state: &mut InputState, _delta: MouseScrollDelta) {} 257 | 258 | fn notify_mouse_click(state: &mut InputState, button: ButtonId, elem_state: ElementState) { 259 | let pressed = matches!(elem_state, ElementState::Pressed); 260 | 261 | // update rising/falling sets 262 | if pressed && state.pressed_buttons.insert(button) { 263 | state.rising_buttons.insert(button); 264 | } else if !pressed && state.pressed_buttons.remove(&button) { 265 | state.falling_buttons.insert(button); 266 | } 267 | } 268 | 269 | fn notify_focus(state: &mut InputState, focus: bool) { 270 | state.focused = focus; 271 | if !focus { 272 | state.grab_cursor(false); 273 | state.hide_cursor(false); 274 | } 275 | } 276 | 277 | pub fn input_compiler( 278 | mut ctx: ResMut, 279 | mut device_events: EventReader, 280 | display: NonSendMut>, 281 | ) { 282 | ctx.rising_keys.clear(); 283 | ctx.falling_keys.clear(); 284 | 285 | ctx.rising_buttons.clear(); 286 | ctx.falling_buttons.clear(); 287 | 288 | ctx.cursor_dx = 0.0; 289 | ctx.cursor_dy = 0.0; 290 | 291 | for event in device_events.iter() { 292 | // do this before we discard events so we can refocus the window 293 | if let &RawInputEvent::Window(_, WindowEvent::Focused(focused)) = event { 294 | notify_focus(&mut ctx, focused) 295 | } 296 | 297 | if !ctx.focused { 298 | continue; 299 | } 300 | 301 | match event { 302 | &RawInputEvent::Device(_, DeviceEvent::MouseMotion { delta }) => { 303 | notify_mouse_motion(&mut ctx, delta.0, delta.1) 304 | } 305 | &RawInputEvent::Device(_, DeviceEvent::MouseWheel { delta }) => { 306 | notify_mouse_scroll(&mut ctx, delta) 307 | } 308 | &RawInputEvent::Device(_, DeviceEvent::Key(input)) => { 309 | notify_keyboard_input(&mut ctx, input) 310 | } 311 | &RawInputEvent::Device(_, DeviceEvent::Button { button, state }) => { 312 | notify_mouse_click(&mut ctx, button, state) 313 | } 314 | 315 | _ => {} 316 | } 317 | } 318 | 319 | let window = display.gl_window(); 320 | let should_grab = ctx.cursor_should_be_grabbed.load(Ordering::SeqCst); 321 | let should_hide = ctx.cursor_should_be_hidden.load(Ordering::SeqCst); 322 | 323 | if ctx.cursor_currently_grabbed != should_grab { 324 | ctx.cursor_currently_grabbed = should_grab; 325 | window.window().set_cursor_grab(should_grab).unwrap(); 326 | } 327 | 328 | if ctx.cursor_currently_hidden != should_hide { 329 | ctx.cursor_currently_hidden = should_hide; 330 | window.window().set_cursor_visible(!should_hide); 331 | } 332 | } 333 | 334 | #[derive(Clone, Debug)] 335 | pub enum RawInputEvent { 336 | Window(WindowId, WindowEvent<'static>), 337 | Device(DeviceId, DeviceEvent), 338 | } 339 | 340 | #[derive(Debug, Default)] 341 | pub struct InputPlugin {} 342 | 343 | impl Plugin for InputPlugin { 344 | fn build(&self, app: &mut AppBuilder) { 345 | app.init_resource::(); 346 | app.add_event::(); 347 | app.add_system_to_stage(CoreStage::PreUpdate, input_compiler.system()); 348 | } 349 | } 350 | -------------------------------------------------------------------------------- /notcraft-client/src/client/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod audio; 2 | pub mod camera; 3 | pub mod debug; 4 | pub mod input; 5 | pub mod loader; 6 | pub mod render; 7 | -------------------------------------------------------------------------------- /notcraft-client/src/client/render/mesher/mod.rs: -------------------------------------------------------------------------------- 1 | use crate::client::{ 2 | debug::MesherEvent, 3 | render::renderer::{MeshBuffers, RenderMeshComponent, SharedMeshContext, UploadableMesh}, 4 | }; 5 | use anyhow::Result; 6 | use crossbeam_channel::{Receiver, Sender}; 7 | use glium::{backend::Facade, index::PrimitiveType, IndexBuffer, VertexBuffer}; 8 | use notcraft_common::{ 9 | aabb::Aabb, 10 | debug::send_debug_event, 11 | prelude::*, 12 | world::{ 13 | chunk::{ChunkData, ChunkSectionPos, ChunkSectionSnapshot, CHUNK_LENGTH}, 14 | lighting::LightValue, 15 | registry::BlockId, 16 | VoxelWorld, 17 | }, 18 | Faces, Side, 19 | }; 20 | use std::{collections::HashSet, str::FromStr, sync::Arc}; 21 | 22 | use self::{ 23 | generation::{should_add_face, ChunkNeighbors, CompletedMesh, MeshCreationContext}, 24 | tracker::{update_tracker, MeshTracker}, 25 | }; 26 | 27 | pub mod generation; 28 | pub mod tracker; 29 | 30 | #[derive(Debug)] 31 | pub struct MesherContext { 32 | completed_meshes: HashSet, 33 | 34 | mesh_tx: Sender, 35 | mesh_rx: Receiver, 36 | mode: MesherMode, 37 | } 38 | 39 | impl MesherContext { 40 | fn new(mode: MesherMode) -> Self { 41 | let (mesh_tx, mesh_rx) = crossbeam_channel::unbounded(); 42 | Self { 43 | completed_meshes: Default::default(), 44 | mesh_tx, 45 | mesh_rx, 46 | mode, 47 | } 48 | } 49 | } 50 | 51 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 52 | pub enum MesherMode { 53 | Simple, 54 | /// greedy meshing doesn't play well with randomized textures 55 | Greedy, 56 | } 57 | 58 | impl FromStr for MesherMode { 59 | type Err = anyhow::Error; 60 | 61 | fn from_str(s: &str) -> Result { 62 | Ok(match s { 63 | "simple" => Self::Simple, 64 | "greedy" => Self::Greedy, 65 | other => bail!("unknown mesher mode '{}'", other), 66 | }) 67 | } 68 | } 69 | 70 | #[derive(Debug)] 71 | pub struct ChunkMesherPlugin { 72 | pub mode: MesherMode, 73 | } 74 | 75 | impl ChunkMesherPlugin { 76 | pub fn with_mode(mut self, mode: MesherMode) -> Self { 77 | self.mode = mode; 78 | self 79 | } 80 | } 81 | 82 | impl Default for ChunkMesherPlugin { 83 | fn default() -> Self { 84 | Self { 85 | mode: MesherMode::Simple, 86 | } 87 | } 88 | } 89 | 90 | impl Plugin for ChunkMesherPlugin { 91 | fn build(&self, app: &mut AppBuilder) { 92 | app.insert_resource(MeshTracker::default()); 93 | app.insert_resource(MesherContext::new(self.mode)); 94 | app.add_system(update_tracker.system()); 95 | app.add_system(queue_mesh_jobs.system()); 96 | app.add_system(update_completed_meshes.system()); 97 | } 98 | } 99 | 100 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] 101 | pub struct HasTerrainMesh; 102 | 103 | fn update_completed_meshes( 104 | mut cmd: Commands, 105 | ctx: Res, 106 | mut tracker: ResMut, 107 | voxel_world: Res>, 108 | mesh_context: Res>>, 109 | ) { 110 | for completed in ctx.mesh_rx.try_iter() { 111 | match completed { 112 | CompletedMesh::Completed { pos, terrain } => { 113 | if let Some(entity) = tracker.terrain_entity(pos) { 114 | if voxel_world.section(pos).is_some() { 115 | let mesh_handle = mesh_context.upload(terrain); 116 | cmd.entity(entity) 117 | .insert(RenderMeshComponent::new(mesh_handle)); 118 | } 119 | } 120 | } 121 | CompletedMesh::Failed { pos } => tracker.chunk_mesh_failed(pos), 122 | } 123 | } 124 | } 125 | 126 | fn homogenous_should_mesh( 127 | world: &Arc, 128 | id: BlockId, 129 | pos: ChunkSectionPos, 130 | ) -> Option { 131 | let faces = Faces { 132 | top: match world.section(pos.offset([0, 1, 0]))?.snapshot().blocks() { 133 | &ChunkData::Homogeneous(nid) => should_add_face(&world.registry, id, nid), 134 | _ => true, 135 | }, 136 | bottom: match world.section(pos.offset([0, -1, 0]))?.snapshot().blocks() { 137 | &ChunkData::Homogeneous(nid) => should_add_face(&world.registry, id, nid), 138 | _ => true, 139 | }, 140 | right: match world.section(pos.offset([1, 0, 0]))?.snapshot().blocks() { 141 | &ChunkData::Homogeneous(nid) => should_add_face(&world.registry, id, nid), 142 | _ => true, 143 | }, 144 | left: match world.section(pos.offset([-1, 0, 0]))?.snapshot().blocks() { 145 | &ChunkData::Homogeneous(nid) => should_add_face(&world.registry, id, nid), 146 | _ => true, 147 | }, 148 | front: match world.section(pos.offset([0, 0, 1]))?.snapshot().blocks() { 149 | &ChunkData::Homogeneous(nid) => should_add_face(&world.registry, id, nid), 150 | _ => true, 151 | }, 152 | back: match world.section(pos.offset([0, 0, -1]))?.snapshot().blocks() { 153 | &ChunkData::Homogeneous(nid) => should_add_face(&world.registry, id, nid), 154 | _ => true, 155 | }, 156 | }; 157 | Some(faces.any(|&face| face)) 158 | } 159 | 160 | fn queue_mesh_job(ctx: &mut MesherContext, world: &Arc, chunk: &ChunkSectionSnapshot) { 161 | let world = Arc::clone(world); 162 | let sender = ctx.mesh_tx.clone(); 163 | let pos = chunk.pos(); 164 | let mode = ctx.mode; 165 | 166 | // note that we explicittly dont move the locked chunk to the new thread, 167 | // because otherwise we would keep the chunk locked while no progress on 168 | // meshing the chunk would be made. 169 | rayon::spawn(move || { 170 | if let Some(neighbors) = ChunkNeighbors::lock(&world, pos) { 171 | let mesher = MeshCreationContext::new(pos, neighbors, &world.registry); 172 | match mode { 173 | MesherMode::Simple => mesher.mesh_simple(sender), 174 | MesherMode::Greedy => mesher.mesh_greedy(sender), 175 | } 176 | send_debug_event(MesherEvent::Meshed { cheap: false, pos }); 177 | } else { 178 | sender.send(CompletedMesh::Failed { pos }).unwrap(); 179 | send_debug_event(MesherEvent::MeshFailed(pos)); 180 | } 181 | }); 182 | 183 | ctx.completed_meshes.insert(pos); 184 | } 185 | 186 | // returns true if this mesh job was "cheap", meaning that this job shoudln't 187 | // count towards the number of meshed chunks this frame. 188 | fn mesh_one( 189 | ctx: &mut MesherContext, 190 | world: &Arc, 191 | chunk: &ChunkSectionSnapshot, 192 | ) -> bool { 193 | let pos = chunk.pos(); 194 | match chunk.blocks() { 195 | &ChunkData::Homogeneous(id) => match homogenous_should_mesh(world, id, pos) { 196 | Some(true) => queue_mesh_job(ctx, world, chunk), 197 | Some(false) | None => { 198 | send_debug_event(MesherEvent::Meshed { cheap: true, pos }); 199 | return true; 200 | } 201 | }, 202 | 203 | ChunkData::Array(_) => queue_mesh_job(ctx, world, chunk), 204 | } 205 | 206 | false 207 | } 208 | 209 | fn queue_mesh_jobs( 210 | mut ctx: ResMut, 211 | mut tracker: ResMut, 212 | voxel_world: Res>, 213 | ) { 214 | let mut remaining_this_frame = 4; 215 | 216 | while remaining_this_frame > 0 { 217 | let chunk = match tracker.next(&voxel_world).map(|chunk| chunk.snapshot()) { 218 | Some(chunk) => chunk, 219 | None => break, 220 | }; 221 | if !mesh_one(&mut ctx, &voxel_world, &chunk) { 222 | remaining_this_frame -= 1; 223 | } 224 | } 225 | } 226 | 227 | #[derive(Copy, Clone, Debug, PartialEq, Default)] 228 | #[repr(C)] 229 | pub struct TerrainVertex { 230 | // - 10 bits for each position 231 | // 5 bits of precisions gets 1-block resolution, an additonal 5 bits gets 16 subdivisions of a 232 | // block. 233 | // - 2 bits for AO 234 | // AO only has 3 possible values, [0,3] 235 | // lower AO values mean darker shadows 236 | pub pos_ao: u32, 237 | 238 | // - 4 bits for sky light 239 | // - 4 bits for block light 240 | // - 1 bit for wind sway 241 | // (4 bit residual) 242 | // - 1 bit for side 243 | // - 2 bits for axis 244 | // we can compute the UV coordinates from the surface normal and the world position, and we can 245 | // get the normal via a lookup table using the side 246 | // - 16 bits for block id 247 | // this seems substantial enough to never ever be a problem 248 | pub light_flags_side_id: u32, 249 | } 250 | 251 | glium::implement_vertex!(TerrainVertex, pos_ao, light_flags_side_id); 252 | 253 | fn pack_side(side: Side) -> u8 { 254 | match side { 255 | // sides with positive facing normals wrt their own axes have a 0 in their MSB 256 | Side::Top => 0b001, 257 | Side::Left => 0b000, 258 | Side::Front => 0b010, 259 | // sides with negative facing normals have a 1 in their MSB 260 | Side::Bottom => 0b101, 261 | Side::Right => 0b100, 262 | Side::Back => 0b110, 263 | } 264 | } 265 | 266 | impl TerrainVertex { 267 | pub fn pack( 268 | pos: [u16; 3], 269 | wind_sway: bool, 270 | side: Side, 271 | light: LightValue, 272 | id: u16, 273 | ao: u8, 274 | ) -> Self { 275 | let [x, y, z] = pos; 276 | let mut pos_ao = 0u32; 277 | // while 10 bits are reserved for each axis, we only use 5 of them currently. 278 | // xxxx xXXX XXyy yyyY YYYY zzzz zZZZ ZZAA 279 | pos_ao |= x as u32 & 0x7ff; 280 | pos_ao <<= 10; 281 | pos_ao |= y as u32 & 0x7ff; 282 | pos_ao <<= 10; 283 | pos_ao |= z as u32 & 0x7ff; 284 | pos_ao <<= 2; 285 | pos_ao |= ao as u32; 286 | 287 | // SSSS BBBB f... .DSS IIII IIII IIII IIII 288 | let mut light_flags_side_id = 0u32; 289 | light_flags_side_id |= (light.raw() as u32) << 8; 290 | light_flags_side_id |= (wind_sway as u32) << 7; 291 | light_flags_side_id |= pack_side(side) as u32; 292 | light_flags_side_id <<= 16; 293 | light_flags_side_id |= id as u32; 294 | 295 | Self { 296 | pos_ao, 297 | light_flags_side_id, 298 | } 299 | } 300 | } 301 | 302 | #[derive(Clone, Debug, PartialEq, Default)] 303 | pub struct TerrainTransparencyMesh { 304 | vertices: Vec, 305 | // TODO: use u16s when possible 306 | indices: Vec, 307 | } 308 | 309 | impl UploadableMesh for TerrainTransparencyMesh { 310 | type Vertex = TerrainVertex; 311 | 312 | fn upload(&self, ctx: &F) -> Result> { 313 | Ok(MeshBuffers { 314 | vertices: VertexBuffer::immutable(ctx, &self.vertices)?, 315 | indices: IndexBuffer::immutable(ctx, PrimitiveType::TrianglesList, &self.indices)?, 316 | 317 | aabb: Aabb { 318 | min: point![0.0, 0.0, 0.0], 319 | max: point![ 320 | CHUNK_LENGTH as f32, 321 | CHUNK_LENGTH as f32, 322 | CHUNK_LENGTH as f32 323 | ], 324 | }, 325 | }) 326 | } 327 | } 328 | 329 | #[derive(Clone, Debug, PartialEq, Default)] 330 | pub struct TerrainMesh { 331 | vertices: Vec, 332 | // TODO: use u16s when possible 333 | indices: Vec, 334 | } 335 | 336 | impl UploadableMesh for TerrainMesh { 337 | type Vertex = TerrainVertex; 338 | 339 | fn upload(&self, ctx: &F) -> Result> { 340 | Ok(MeshBuffers { 341 | vertices: VertexBuffer::immutable(ctx, &self.vertices)?, 342 | indices: IndexBuffer::immutable(ctx, PrimitiveType::TrianglesList, &self.indices)?, 343 | 344 | aabb: Aabb { 345 | min: point![0.0, 0.0, 0.0], 346 | max: point![ 347 | CHUNK_LENGTH as f32, 348 | CHUNK_LENGTH as f32, 349 | CHUNK_LENGTH as f32 350 | ], 351 | }, 352 | }) 353 | } 354 | } 355 | -------------------------------------------------------------------------------- /notcraft-client/src/client/render/mesher/tracker.rs: -------------------------------------------------------------------------------- 1 | //! this module provides tracking for which chunks need to be meshed. 2 | //! 3 | //! this tracking is needed because we can only mesh a chunk properly when all 4 | //! 26 neighboring chunks are loaded, so directly meshing a chunk when a ["chunk 5 | //! added" event][crate::common::world::ChunkEvent] is received is off the 6 | //! table. tracking is handled by [`MeshTracker`], which receives updates about 7 | //! the state of the world, and produces positions of chunks that have enough 8 | //! data to be meshed. 9 | 10 | use std::{ 11 | collections::{HashMap, HashSet}, 12 | sync::Arc, 13 | }; 14 | 15 | use nalgebra::Point3; 16 | 17 | use notcraft_common::{ 18 | prelude::*, 19 | transform::Transform, 20 | world::{ 21 | chunk::{ChunkSection, ChunkSectionPos}, 22 | VoxelWorld, WorldEvent, 23 | }, 24 | }; 25 | 26 | fn neighbors(pos: ChunkSectionPos, mut func: F) 27 | where 28 | F: FnMut(ChunkSectionPos), 29 | { 30 | for x in pos.x - 1..=pos.x + 1 { 31 | for y in pos.y - 1..=pos.y + 1 { 32 | for z in pos.z - 1..=pos.z + 1 { 33 | let neighbor = ChunkSectionPos { x, y, z }; 34 | if neighbor != pos { 35 | func(neighbor); 36 | } 37 | } 38 | } 39 | } 40 | } 41 | 42 | #[derive(Debug, Default)] 43 | pub struct MeshTracker { 44 | constraining: HashMap>, 45 | constrained_by: HashMap>, 46 | 47 | needs_mesh: HashSet, 48 | 49 | loaded: HashSet, 50 | terrain_entities: HashMap, 51 | } 52 | 53 | impl MeshTracker { 54 | // INVARIANT: if `have_data` does not contain X, then `constrained_by` also does 55 | // not contain X 56 | 57 | // INVARIANT: if `have_data` contains X, then `constraining` does NOT contain X 58 | 59 | // INVARIANT: for a chunk X and each value Y of `constraining[X]`, 60 | // `constrained_by[Y]` must contain X 61 | 62 | fn constrain_self(&mut self, center: ChunkSectionPos) { 63 | neighbors(center, |neighbor| { 64 | if !self.loaded.contains(&neighbor) { 65 | self.constraining 66 | .entry(neighbor) 67 | .or_default() 68 | .insert(center); 69 | self.constrained_by 70 | .entry(center) 71 | .or_default() 72 | .insert(neighbor); 73 | } 74 | }); 75 | } 76 | 77 | fn constrain_neighbors(&mut self, center: ChunkSectionPos) { 78 | neighbors(center, |neighbor| { 79 | if self.loaded.contains(&neighbor) { 80 | self.constraining 81 | .entry(center) 82 | .or_default() 83 | .insert(neighbor); 84 | self.constrained_by 85 | .entry(neighbor) 86 | .or_default() 87 | .insert(center); 88 | 89 | self.needs_mesh.remove(&neighbor); 90 | } 91 | }); 92 | } 93 | 94 | fn unconstrain_self(&mut self, center: ChunkSectionPos) { 95 | let constrainers = match self.constrained_by.remove(¢er) { 96 | Some(constrainers) => constrainers, 97 | None => return, 98 | }; 99 | 100 | for &constrainer in constrainers.iter() { 101 | let neighbor_constraining = self 102 | .constraining 103 | .get_mut(&constrainer) 104 | .expect("(remove) constraints not bidirectional"); 105 | 106 | neighbor_constraining.remove(¢er); 107 | if neighbor_constraining.is_empty() { 108 | self.constraining.remove(&constrainer); 109 | } 110 | } 111 | } 112 | 113 | fn unconstrain_neighbors(&mut self, center: ChunkSectionPos) { 114 | let constraining = match self.constraining.remove(¢er) { 115 | Some(constraining) => constraining, 116 | None => return, 117 | }; 118 | 119 | for &neighbor in constraining.iter() { 120 | let neighbor_constraints = self 121 | .constrained_by 122 | .get_mut(&neighbor) 123 | .expect("(add) constraints not bidirectional"); 124 | 125 | neighbor_constraints.remove(¢er); 126 | if neighbor_constraints.is_empty() { 127 | self.constrained_by.remove(&neighbor); 128 | self.needs_mesh.insert(neighbor); 129 | } 130 | } 131 | } 132 | 133 | pub fn chunk_mesh_failed(&mut self, chunk: ChunkSectionPos) { 134 | // by the time it gets here, the failed chunk might have been unloaded itself, 135 | // or might have had its neighbors been unloaded. if it was unloaded itself, 136 | // there is nothing to do because of the `have_data` invariants. 137 | if !self.loaded.contains(&chunk) { 138 | return; 139 | } 140 | 141 | self.constrain_self(chunk); 142 | 143 | // it might be the case that a mesh failed because of unloaded neighbors, but 144 | // between the time that the failed response was queued and now, the neighbors 145 | // became loaded. 146 | self.request_mesh(chunk); 147 | } 148 | 149 | pub fn add_chunk(&mut self, chunk: ChunkSectionPos, cmd: &mut Commands) { 150 | let success = self.loaded.insert(chunk); 151 | assert!( 152 | success, 153 | "chunk {:?} was added to tracker, but it was already tracked", 154 | chunk 155 | ); 156 | 157 | let world_pos: Point3 = chunk.origin().origin().into(); 158 | let transform = Transform::from(world_pos); 159 | self.terrain_entities 160 | .insert(chunk, cmd.spawn().insert(transform).id()); 161 | 162 | // set up constraints for the newly-added chunk 163 | self.constrain_self(chunk); 164 | 165 | // remove constraints for neighbors that depended on us 166 | self.unconstrain_neighbors(chunk); 167 | 168 | // it may be the case that we get a new chunk where all its neighbors already 169 | // have data, in which case the new chunk is already unconstrained. 170 | self.request_mesh(chunk); 171 | 172 | assert!(!self.constraining.contains_key(&chunk)); 173 | } 174 | 175 | pub fn remove_chunk(&mut self, chunk: ChunkSectionPos, cmd: &mut Commands) { 176 | let success = self.loaded.remove(&chunk); 177 | assert!( 178 | success, 179 | "chunk {:?} was removed from tracker, but it wasn't tracked", 180 | chunk 181 | ); 182 | 183 | let entity = self.terrain_entities.remove(&chunk).unwrap(); 184 | cmd.entity(entity).despawn(); 185 | 186 | // remove old `constraining` entries that pointed to the removed chunk, 187 | // upholding one of our `have_data` invariants. 188 | self.unconstrain_self(chunk); 189 | 190 | // add constraints to neighbors of the newly-removed chunk 191 | self.constrain_neighbors(chunk); 192 | 193 | assert!(!self.constrained_by.contains_key(&chunk)); 194 | } 195 | 196 | pub fn request_mesh(&mut self, chunk: ChunkSectionPos) { 197 | let is_unconstrained = !self.constrained_by.contains_key(&chunk); 198 | let is_loaded = self.loaded.contains(&chunk); 199 | if is_unconstrained && is_loaded { 200 | self.needs_mesh.insert(chunk); 201 | } 202 | } 203 | 204 | pub fn next(&mut self, world: &Arc) -> Option> { 205 | let &pos = self.needs_mesh.iter().next()?; 206 | let chunk = world.section(pos); 207 | assert!( 208 | chunk.is_some(), 209 | "chunk {:?} was tracked for meshing but didnt exist in the world", 210 | pos 211 | ); 212 | assert!( 213 | !self.constrained_by.contains_key(&pos), 214 | "chunk {:?} was in to-mesh set, but was constrained by {:?}", 215 | pos, 216 | self.constrained_by[&pos] 217 | ); 218 | self.needs_mesh.remove(&pos); 219 | chunk 220 | } 221 | 222 | pub fn terrain_entity(&self, pos: ChunkSectionPos) -> Option { 223 | self.terrain_entities.get(&pos).cloned() 224 | } 225 | } 226 | 227 | pub fn update_tracker( 228 | mut cmd: Commands, 229 | mut tracker: ResMut, 230 | mut events: EventReader, 231 | ) { 232 | for event in events.iter() { 233 | match event { 234 | WorldEvent::LoadedSection(chunk) => tracker.add_chunk(chunk.pos(), &mut cmd), 235 | WorldEvent::UnloadedSection(chunk) => tracker.remove_chunk(chunk.pos(), &mut cmd), 236 | WorldEvent::ModifiedSection(chunk) => { 237 | // NOTE: we're choosing to keep chunk meshes for chunks that have already been 238 | // meshed, but no longer have enough data to re-mesh 239 | tracker.request_mesh(chunk.pos()); 240 | } 241 | 242 | _ => {} 243 | } 244 | } 245 | } 246 | -------------------------------------------------------------------------------- /notcraft-client/src/client/render/mod.rs: -------------------------------------------------------------------------------- 1 | pub mod mesher; 2 | pub mod renderer; 3 | 4 | #[repr(C)] 5 | #[derive(Copy, Clone, Debug, PartialEq, Default)] 6 | pub struct Tex { 7 | pub uv: [f32; 2], 8 | } 9 | glium::implement_vertex!(Tex, uv); 10 | 11 | #[repr(C)] 12 | #[derive(Copy, Clone, Debug, PartialEq, Default)] 13 | pub struct PosTex { 14 | pub pos: [f32; 3], 15 | pub uv: [f32; 2], 16 | } 17 | glium::implement_vertex!(PosTex, pos, uv); 18 | -------------------------------------------------------------------------------- /notcraft-client/src/total_float.rs: -------------------------------------------------------------------------------- 1 | #[derive(Copy, Clone, Debug, Default)] 2 | #[repr(transparent)] 3 | pub struct TotalFloat(pub T); 4 | 5 | macro_rules! impl_float_ord { 6 | ($float_type:ty, $int_type:ty) => { 7 | impl TotalFloat<$float_type> { 8 | pub fn as_orderable(value: $float_type) -> $int_type { 9 | const ZERO: $int_type = 0; 10 | const SHIFT: $int_type = (<$int_type>::BITS - 1) as $int_type; 11 | 12 | // from http://stereopsis.com/radix.html 13 | let bits = value.to_bits(); 14 | 15 | // to fix up a floating point number for comparison, we have to invert the sign 16 | // bit, and invert all other bits if the sign bit was set (the number was 17 | // negative). we implement this here with a single XOR mask. 18 | 19 | // this converts a negative sign (sign bit set) to an all-bits-set mask, 20 | // covering our case where we invert all bits if the number was negative. 21 | let mut mask = ZERO.wrapping_sub(bits >> SHIFT); 22 | 23 | // while the negative case is covered, we still need to reverse the sign bit in 24 | // both cases, so we can OR in the sign bit's place in the mask. this has no 25 | // effect on the negative case that is already covered, but makes the positive 26 | // case correct. 27 | mask |= (1 << SHIFT); 28 | 29 | bits ^ mask 30 | } 31 | } 32 | 33 | impl Eq for TotalFloat<$float_type> {} 34 | impl PartialEq for TotalFloat<$float_type> { 35 | fn eq(&self, other: &Self) -> bool { 36 | self.0.to_bits() == other.0.to_bits() 37 | } 38 | } 39 | 40 | impl Ord for TotalFloat<$float_type> { 41 | fn cmp(&self, other: &Self) -> std::cmp::Ordering { 42 | Self::as_orderable(self.0).cmp(&Self::as_orderable(other.0)) 43 | } 44 | } 45 | impl PartialOrd for TotalFloat<$float_type> { 46 | fn partial_cmp(&self, other: &Self) -> Option { 47 | Self::as_orderable(self.0).partial_cmp(&Self::as_orderable(other.0)) 48 | } 49 | } 50 | 51 | impl std::hash::Hash for TotalFloat<$float_type> { 52 | fn hash(&self, state: &mut H) { 53 | self.0.to_bits().hash(state); 54 | } 55 | } 56 | }; 57 | } 58 | 59 | impl_float_ord!(f32, u32); 60 | impl_float_ord!(f64, u64); 61 | 62 | #[cfg(test)] 63 | mod tests { 64 | use super::*; 65 | use std::{ 66 | collections::hash_map::DefaultHasher, 67 | hash::{Hash, Hasher}, 68 | }; 69 | 70 | #[rustfmt::skip] 71 | const F32_EQ_SAMPLES: &[f32] = &[ 72 | 0.0, -0.0, 1.0, -1.0, 73 | f32::MIN, f32::MAX, 74 | f32::INFINITY, f32::NEG_INFINITY, 75 | f32::NAN, -f32::NAN, 76 | ]; 77 | 78 | #[rustfmt::skip] 79 | const F64_EQ_SAMPLES: &[f64] = &[ 80 | 0.0, -0.0, 1.0, -1.0, 81 | f64::MIN, f64::MAX, 82 | f64::INFINITY, f64::NEG_INFINITY, 83 | f64::NAN, -f64::NAN, 84 | ]; 85 | 86 | #[test] 87 | fn test_equality_f32() { 88 | for &number in F32_EQ_SAMPLES.iter() { 89 | assert_eq!(TotalFloat(number), TotalFloat(number)); 90 | } 91 | } 92 | 93 | #[test] 94 | fn test_equality_f64() { 95 | for &number in F64_EQ_SAMPLES.iter() { 96 | assert_eq!(TotalFloat(number), TotalFloat(number)); 97 | } 98 | } 99 | 100 | fn hash(value: H) -> u64 { 101 | let mut hasher = DefaultHasher::new(); 102 | value.hash(&mut hasher); 103 | hasher.finish() 104 | } 105 | 106 | #[test] 107 | fn test_hash_f32() { 108 | for &number in F32_EQ_SAMPLES.iter() { 109 | assert_eq!(hash(TotalFloat(number)), hash(TotalFloat(number))); 110 | } 111 | } 112 | 113 | #[test] 114 | fn test_hash_f64() { 115 | for &number in F64_EQ_SAMPLES.iter() { 116 | assert_eq!(hash(TotalFloat(number)), hash(TotalFloat(number))); 117 | } 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /notcraft-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "notcraft-common" 3 | version = "0.1.0" 4 | edition = "2021" 5 | 6 | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html 7 | 8 | [features] 9 | hot-reload = ["notify"] 10 | debug = ["hot-reload"] 11 | 12 | [dependencies] 13 | lazy_static = "1.3" 14 | log = "0.4" 15 | num-traits = "0.2" 16 | anyhow = "1.0" 17 | serde = "1.0" 18 | serde_derive = "1.0" 19 | serde_json = "1.0" 20 | flurry = "0.3" 21 | noise = "0.7" 22 | rayon = "1.0" 23 | crossbeam-channel = "0.5" 24 | rand = "0.6" 25 | nalgebra = "0.29.0" 26 | approx = "0.5" 27 | 28 | bevy_core = "0.5" 29 | bevy_ecs = "0.5" 30 | bevy_app = "0.5" 31 | 32 | arc-swap = "1.5" 33 | notify = { version = "5.0.0-pre.13", optional = true } 34 | 35 | bumpalo = "3.9.1" 36 | 37 | # NOTE: the `send_guard` feature is important because we use raw rwlocks in chunk management code, 38 | # where it is very possible that locked chunks get send across thread boundaries, such that a raw 39 | # unlock happens on a different thread than the raw lock. 40 | parking_lot = { version = "0.11", features = ["send_guard"] } 41 | -------------------------------------------------------------------------------- /notcraft-common/src/aabb.rs: -------------------------------------------------------------------------------- 1 | use crate::{math::*, util, vector}; 2 | 3 | use super::transform::Transform; 4 | 5 | #[rustfmt::skip] 6 | fn spans_overlap(amin: f32, amax: f32, bmin: f32, bmax: f32) -> bool { 7 | util::is_between(bmin, amin, amax) || util::is_between(amin, bmin, bmax) || 8 | util::is_between(bmax, amin, amax) || util::is_between(amax, bmin, bmax) 9 | } 10 | 11 | #[derive(Copy, Clone, Debug, PartialEq)] 12 | pub struct Aabb { 13 | pub min: Point3, 14 | pub max: Point3, 15 | } 16 | 17 | impl Aabb { 18 | pub fn with_dimensions(dims: Vector3) -> Self { 19 | let half_dims = dims / 2.0; 20 | Aabb { 21 | min: Point3::from(-half_dims), 22 | max: Point3::from(half_dims), 23 | } 24 | } 25 | 26 | #[rustfmt::skip] 27 | pub fn contains(&self, point: &Point3) -> bool { 28 | util::is_between(point.x, self.min.x, self.max.x) && 29 | util::is_between(point.y, self.min.y, self.max.y) && 30 | util::is_between(point.z, self.min.z, self.max.z) 31 | } 32 | 33 | #[rustfmt::skip] 34 | pub fn intersects(&self, other: &Aabb) -> bool { 35 | spans_overlap(self.min.x, self.max.x, other.min.x, other.max.x) && 36 | spans_overlap(self.min.y, self.max.y, other.min.y, other.max.y) && 37 | spans_overlap(self.min.z, self.max.z, other.min.z, other.max.z) 38 | } 39 | 40 | pub fn dimensions(&self) -> Vector3 { 41 | vector![ 42 | self.max.x - self.min.x, 43 | self.max.y - self.min.y, 44 | self.max.z - self.min.z 45 | ] 46 | } 47 | 48 | pub fn center(&self) -> Point3 { 49 | self.min + self.dimensions() / 2.0 50 | } 51 | 52 | pub fn translated(&self, translation: Vector3) -> Aabb { 53 | Aabb { 54 | min: self.min + translation, 55 | max: self.max + translation, 56 | } 57 | } 58 | 59 | pub fn inflate(&self, distance: f32) -> Aabb { 60 | Aabb { 61 | min: self.min - vector![distance, distance, distance], 62 | max: self.max + vector![distance, distance, distance], 63 | } 64 | } 65 | 66 | pub fn transformed(&self, transform: &Transform) -> Aabb { 67 | // you can think of this as a vector based at `self.min`, with its tip in the 68 | // center of the AABB. 69 | let half_dimensions = self.dimensions() / 2.0; 70 | // translate our center to the new center 71 | let center = self.min + half_dimensions; 72 | let center = transform.translation * center; 73 | // the whole reason we couln't just add the transform's translation is because 74 | // scaling the AABB when its center is not at the origin would have a 75 | // translating sort of effect. here we just scale the "to center" vector by the 76 | // scale and define the new AABB as displacements from the translated center 77 | let corner_displacement = half_dimensions.component_mul(&transform.scale); 78 | Aabb { 79 | min: center - corner_displacement, 80 | max: center + corner_displacement, 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /notcraft-common/src/codec/decode.rs: -------------------------------------------------------------------------------- 1 | // fn read_one_byte(reader: &mut R) -> Result { 2 | // let mut buf = [0]; 3 | // reader.read_exact(&mut buf)?; 4 | // Ok(buf[0]) 5 | // } 6 | 7 | // fn decode_u8(reader: &mut R) -> Result { 8 | // let mut cur = 0; 9 | // let mut shift = 0; 10 | 11 | // loop { 12 | // let octet = read_one_byte(reader)?; 13 | // cur |= ((octet & 0x7f) as u8) << shift; 14 | // shift += 7; 15 | 16 | // if octet & 0x80 == 0 { 17 | // break; 18 | // } 19 | // } 20 | 21 | // Ok(cur) 22 | // } 23 | 24 | // fn decode_i8(reader: &mut R) -> Result { 25 | // let mut cur = 0; 26 | // let mut shift = 0; 27 | 28 | // loop { 29 | // let octet = read_one_byte(reader)?; 30 | // match octet & 0x80 != 0 { 31 | // true => { 32 | // cur |= ((octet & 0x7f) as i8) << shift; 33 | // shift += 7; 34 | // } 35 | // false => { 36 | // let sign = octet & 0x40 != 0; 37 | // cur |= ((octet & 0x3f) as i8) << shift; 38 | // cur *= [1, -1][sign as usize]; 39 | // break; 40 | // } 41 | // } 42 | // } 43 | 44 | // Ok(cur) 45 | // } 46 | -------------------------------------------------------------------------------- /notcraft-common/src/codec/encode.rs: -------------------------------------------------------------------------------- 1 | use super::{ListKind, NodeKind}; 2 | use crate::prelude::*; 3 | use std::io::Write; 4 | 5 | trait BaseEncode { 6 | fn encode(&self, writer: &mut W) -> Result<()>; 7 | } 8 | 9 | pub trait Encode { 10 | const KIND: NodeKind; 11 | fn encode(&self, encoder: Encoder) -> Result<()>; 12 | } 13 | 14 | pub struct MapEncoder<'w, W> { 15 | writer: &'w mut W, 16 | } 17 | 18 | impl<'w, W: Write> MapEncoder<'w, W> { 19 | // see `mapNode` in module-level documentation for format specification 20 | pub fn entry<'e, 'k>(&'e mut self, key: &'k str) -> MapEncoderEntry<'w, 'e, 'k, W> { 21 | assert!(!key.is_empty(), "tried encoding map entry with empty key"); 22 | MapEncoderEntry { encoder: self, key } 23 | } 24 | } 25 | 26 | pub struct MapEncoderEntry<'w, 'e, 's, W> { 27 | encoder: &'e mut MapEncoder<'w, W>, 28 | key: &'s str, 29 | } 30 | 31 | impl<'w, W: Write> MapEncoderEntry<'w, '_, '_, W> { 32 | fn encode_header(&mut self, kind: NodeKind) -> Result<()> { 33 | encode_base(self.encoder.writer, self.key)?; 34 | encode_base(self.encoder.writer, &FixedInt(kind as u8))?; 35 | Ok(()) 36 | } 37 | 38 | // see `mapNode` in module-level documentation for format specification 39 | pub fn encode>(mut self, item: &T) -> Result<()> { 40 | self.encode_header(>::KIND)?; 41 | encode(self.encoder.writer, item)?; 42 | Ok(()) 43 | } 44 | 45 | pub fn encode_verbatim_list(mut self, iter: I) -> Result<()> 46 | where 47 | I: ExactSizeIterator, 48 | I::Item: Encode, 49 | { 50 | self.encode_header(NodeKind::List)?; 51 | encode_verbatim_list(self.encoder.writer, iter)?; 52 | Ok(()) 53 | } 54 | 55 | pub fn encode_rle_list(mut self, iter: I) -> Result<()> 56 | where 57 | I: Iterator, 58 | I::Item: Encode + PartialEq, 59 | { 60 | self.encode_header(NodeKind::List)?; 61 | encode_rle_list(self.encoder.writer, iter)?; 62 | Ok(()) 63 | } 64 | 65 | pub fn encode_rle_list_runs(mut self, iter: I) -> Result<()> 66 | where 67 | I: Iterator, 68 | T: Encode + PartialEq, 69 | { 70 | self.encode_header(NodeKind::List)?; 71 | encode_rle_list_runs(self.encoder.writer, iter)?; 72 | Ok(()) 73 | } 74 | 75 | pub fn encode_map(mut self, func: F) -> Result<()> 76 | where 77 | F: FnOnce(MapEncoder<'_, W>) -> Result<()>, 78 | { 79 | self.encode_header(NodeKind::Map)?; 80 | encode_map(self.encoder.writer, func)?; 81 | Ok(()) 82 | } 83 | } 84 | 85 | // TODO: currently, there is no verification that you write the same kind of 86 | // node that's specified in [`Encode::KIND`] 87 | pub struct Encoder<'w, W> { 88 | writer: &'w mut W, 89 | } 90 | 91 | impl<'w, W: Write> Encoder<'w, W> { 92 | pub fn encode>(self, item: &T) -> Result<()> { 93 | encode(self.writer, item) 94 | } 95 | 96 | pub fn encode_verbatim_list(self, iter: I) -> Result<()> 97 | where 98 | I: ExactSizeIterator, 99 | I::Item: Encode, 100 | { 101 | encode_verbatim_list(self.writer, iter) 102 | } 103 | 104 | pub fn encode_rle_list(self, iter: I) -> Result<()> 105 | where 106 | I: Iterator, 107 | I::Item: Encode + PartialEq, 108 | { 109 | encode_rle_list(self.writer, iter) 110 | } 111 | 112 | pub fn encode_rle_list_runs(self, iter: I) -> Result<()> 113 | where 114 | I: Iterator, 115 | T: Encode + PartialEq, 116 | { 117 | encode_rle_list_runs(self.writer, iter) 118 | } 119 | 120 | pub fn encode_map(self, func: F) -> Result<()> 121 | where 122 | F: FnOnce(MapEncoder<'_, W>) -> Result<()>, 123 | { 124 | encode_map(self.writer, func) 125 | } 126 | } 127 | 128 | fn encode_base + ?Sized>(writer: &mut W, item: &T) -> Result<()> { 129 | T::encode(item, writer) 130 | } 131 | 132 | fn encode + ?Sized>(writer: &mut W, item: &T) -> Result<()> { 133 | T::encode(item, Encoder { writer }) 134 | } 135 | 136 | fn encode_verbatim_list(writer: &mut W, mut iter: I) -> Result<()> 137 | where 138 | I: ExactSizeIterator, 139 | I::Item: Encode, 140 | W: Write, 141 | { 142 | // see `verbatimListNode` in module-level documentation for format specification 143 | encode_base(writer, &FixedInt(ListKind::Verbatim as u8))?; 144 | encode_base(writer, &VarInt(iter.len() as u32))?; 145 | encode_base(writer, &FixedInt(>::KIND as u8))?; 146 | 147 | for item in iter.by_ref() { 148 | encode(writer, &item)?; 149 | } 150 | 151 | Ok(()) 152 | } 153 | 154 | fn encode_rle_list(writer: &mut W, mut iter: I) -> Result<()> 155 | where 156 | I: Iterator, 157 | I::Item: Encode + PartialEq, 158 | W: Write, 159 | { 160 | // see `rleListNode` in module-level documentation for format specification 161 | encode_base(writer, &FixedInt(ListKind::RunLength as u8))?; 162 | encode_base(writer, &FixedInt(>::KIND as u8))?; 163 | 164 | if let Some(first_item) = iter.next() { 165 | let mut current_run_len = 1usize; 166 | let mut current_run_element = first_item; 167 | 168 | for element in iter { 169 | if current_run_element != element { 170 | encode_base(writer, &VarInt(current_run_len))?; 171 | encode(writer, ¤t_run_element)?; 172 | current_run_len = 1; 173 | current_run_element = element; 174 | } else { 175 | current_run_len += 1; 176 | } 177 | } 178 | 179 | encode_base(writer, &VarInt(current_run_len))?; 180 | encode(writer, ¤t_run_element)?; 181 | } 182 | 183 | encode_base(writer, &VarInt(0u8))?; 184 | 185 | Ok(()) 186 | } 187 | 188 | fn encode_rle_list_runs(writer: &mut W, mut iter: I) -> Result<()> 189 | where 190 | I: Iterator, 191 | T: Encode + PartialEq, 192 | W: Write, 193 | { 194 | encode_base(writer, &FixedInt(ListKind::RunLength as u8))?; 195 | 196 | // see `rleListNode` in module-level documentation for format specification 197 | encode_base(writer, &FixedInt(>::KIND as u8))?; 198 | 199 | while let Some((len, item)) = iter.next() { 200 | encode_base(writer, &VarInt(len))?; 201 | encode(writer, &item)?; 202 | } 203 | 204 | encode_base(writer, &VarInt(0u8))?; 205 | 206 | Ok(()) 207 | } 208 | 209 | fn encode_map(writer: &mut W, func: F) -> Result<()> 210 | where 211 | F: FnOnce(MapEncoder<'_, W>) -> Result<()>, 212 | W: Write, 213 | { 214 | // see `mapNode` in module-level documentation for format specification 215 | func(MapEncoder { writer })?; 216 | encode_base(writer, &VarInt(0u8))?; 217 | Ok(()) 218 | } 219 | 220 | impl> Encode for &'_ T { 221 | const KIND: NodeKind = >::KIND; 222 | 223 | fn encode(&self, encoder: Encoder) -> Result<()> { 224 | >::encode(&**self, encoder) 225 | } 226 | } 227 | 228 | impl> Encode for &'_ mut T { 229 | const KIND: NodeKind = >::KIND; 230 | 231 | fn encode(&self, encoder: Encoder) -> Result<()> { 232 | >::encode(&**self, encoder) 233 | } 234 | } 235 | 236 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] 237 | pub struct Verbatim(pub T); 238 | 239 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] 240 | pub struct RunLength(pub T); 241 | 242 | impl> Encode for Verbatim> { 243 | const KIND: NodeKind = NodeKind::List; 244 | 245 | fn encode(&self, encoder: Encoder) -> Result<()> { 246 | encoder.encode_verbatim_list(self.0.iter()) 247 | } 248 | } 249 | 250 | impl + PartialEq> Encode for RunLength> { 251 | const KIND: NodeKind = NodeKind::List; 252 | 253 | fn encode(&self, encoder: Encoder) -> Result<()> { 254 | encoder.encode_rle_list(self.0.iter()) 255 | } 256 | } 257 | 258 | impl<'a, W: std::io::Write, T: Encode> Encode for Verbatim<&'a [T]> { 259 | const KIND: NodeKind = NodeKind::List; 260 | 261 | fn encode(&self, encoder: Encoder) -> Result<()> { 262 | encoder.encode_verbatim_list(self.0.iter()) 263 | } 264 | } 265 | 266 | impl<'a, W: std::io::Write, T: Encode + PartialEq> Encode for RunLength<&'a [T]> { 267 | const KIND: NodeKind = NodeKind::List; 268 | 269 | fn encode(&self, encoder: Encoder) -> Result<()> { 270 | encoder.encode_rle_list(self.0.iter()) 271 | } 272 | } 273 | 274 | impl BaseEncode for str { 275 | fn encode(&self, writer: &mut W) -> Result<()> { 276 | <_ as BaseEncode>::encode(&VarInt(self.len()), writer)?; 277 | writer.write_all(self.as_bytes())?; 278 | Ok(()) 279 | } 280 | } 281 | 282 | impl Encode for str { 283 | const KIND: NodeKind = NodeKind::String; 284 | 285 | fn encode(&self, encoder: Encoder) -> Result<()> { 286 | >::encode(&self, encoder.writer) 287 | } 288 | } 289 | 290 | impl BaseEncode for bool { 291 | fn encode(&self, writer: &mut W) -> Result<()> { 292 | writer.write_all(&[*self as u8])?; 293 | Ok(()) 294 | } 295 | } 296 | 297 | impl Encode for bool { 298 | const KIND: NodeKind = NodeKind::Bool; 299 | 300 | fn encode(&self, encoder: Encoder) -> Result<()> { 301 | >::encode(&self, encoder.writer) 302 | } 303 | } 304 | 305 | impl BaseEncode for f32 { 306 | fn encode(&self, writer: &mut W) -> Result<()> { 307 | writer.write_all(&self.to_be_bytes())?; 308 | Ok(()) 309 | } 310 | } 311 | 312 | impl Encode for f32 { 313 | const KIND: NodeKind = NodeKind::Float32; 314 | 315 | fn encode(&self, encoder: Encoder) -> Result<()> { 316 | >::encode(&self, encoder.writer) 317 | } 318 | } 319 | 320 | impl BaseEncode for f64 { 321 | fn encode(&self, writer: &mut W) -> Result<()> { 322 | writer.write_all(&self.to_be_bytes())?; 323 | Ok(()) 324 | } 325 | } 326 | 327 | impl Encode for f64 { 328 | const KIND: NodeKind = NodeKind::Float64; 329 | 330 | fn encode(&self, encoder: Encoder) -> Result<()> { 331 | >::encode(&self, encoder.writer) 332 | } 333 | } 334 | 335 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] 336 | #[repr(transparent)] 337 | pub struct FixedInt(pub T); 338 | 339 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Ord, PartialOrd, Hash, Default)] 340 | pub struct VarInt(pub T); 341 | 342 | macro_rules! impl_fixed_numeric_encode { 343 | ($($type:ty,)*) => { 344 | $(impl BaseEncode for FixedInt<$type> { 345 | fn encode(&self, writer: &mut W) -> Result<()> { 346 | writer.write_all(&self.0.to_be_bytes())?; 347 | Ok(()) 348 | } 349 | })* 350 | }; 351 | } 352 | 353 | impl_fixed_numeric_encode! { u8, u16, u32, u64, u128, i8, i16, i32, i64, i128, } 354 | 355 | macro_rules! impl_numeric_encode { 356 | (@encode $kind:expr, $($type:ty,)*) => { 357 | $(impl Encode for $type { 358 | const KIND: NodeKind = $kind; 359 | 360 | fn encode(&self, encoder: Encoder) -> Result<()> { 361 | as BaseEncode>::encode(&VarInt(*self), encoder.writer) 362 | } 363 | })* 364 | }; 365 | 366 | (signed { $($type:ty,)* }; $($rest:tt)*) => { 367 | $(impl BaseEncode for VarInt<$type> { 368 | fn encode(&self, writer: &mut W) -> Result<()> { 369 | let VarInt(value) = *self; 370 | let (sign, unsigned) = match value >= 0 { 371 | true => (0, value as u8), 372 | false => (0x40, value.abs() as u8), 373 | }; 374 | 375 | let mut cur = unsigned; 376 | while cur > 0x3f { 377 | writer.write_all(&[0x80 | (cur & 0x7f) as u8])?; 378 | cur >>= 7; 379 | } 380 | writer.write_all(&[sign | cur as u8])?; 381 | 382 | Ok(()) 383 | } 384 | })* 385 | 386 | impl_numeric_encode! { @encode NodeKind::SignedVarInt, $($type,)* } 387 | impl_numeric_encode! { $($rest)* } 388 | }; 389 | 390 | (unsigned { $($type:ty,)* }; $($rest:tt)*) => { 391 | $(impl BaseEncode for VarInt<$type> { 392 | fn encode(&self, writer: &mut W) -> Result<()> { 393 | let VarInt(mut cur) = *self; 394 | while cur > 0x7f { 395 | writer.write_all(&[0x80 | (cur & 0x7f) as u8])?; 396 | cur >>= 7; 397 | } 398 | writer.write_all(&[cur as u8])?; 399 | 400 | Ok(()) 401 | } 402 | })* 403 | 404 | impl_numeric_encode! { @encode NodeKind::UnsignedVarInt, $($type,)* } 405 | impl_numeric_encode! { $($rest)* } 406 | }; 407 | 408 | () => {}; 409 | } 410 | 411 | impl_numeric_encode! { 412 | signed { i8, i16, i32, i64, i128, isize, }; 413 | unsigned { u8, u16, u32, u64, u128, usize, }; 414 | } 415 | -------------------------------------------------------------------------------- /notcraft-common/src/codec/mod.rs: -------------------------------------------------------------------------------- 1 | //! This module provides facilities for encoding and decoding a custom binary 2 | //! save format. 3 | //! 4 | //! # Format Description 5 | //! 6 | //! ```no_run 7 | //! def NODE_TYPE_NODE = 0:u8 8 | //! def NODE_TYPE_LIST = 1:u8 9 | //! def NODE_TYPE_MAP = 2:u8 10 | //! def NODE_TYPE_RAW = 3:u8 11 | //! def NODE_TYPE_STRING = 4:u8 12 | //! def NODE_TYPE_BOOL = 5:u8 13 | //! def NODE_TYPE_UNSIGNED = 6:u8 14 | //! def NODE_TYPE_SIGNED = 7:u8 15 | //! def NODE_TYPE_F32 = 8:u8 16 | //! def NODE_TYPE_F64 = 9:u8 17 | //! 18 | //! def unsingedVarInt = /* variable-length quantity */ 19 | //! def singedVarInt = /* variable-length quantity */ 20 | //! 21 | //! // line a byteSequence, but has the additional constraint of being valid UTF-8 22 | //! def string = length:unsingedVarInt ~ data:{u8{length}} 23 | //! def byteSequence = length:unsingedVarInt ~ data:{u8{length}} 24 | //! 25 | //! def root = 26 | //! ~ formatVersion:u64be 27 | //! ~ rootNode:mapNode 28 | //! 29 | //! def node = 30 | //! / NODE_TYPE_NODE ~ node 31 | //! / NODE_TYPE_MAP ~ mapNode 32 | //! / NODE_TYPE_LIST ~ listNode 33 | //! / NODE_TYPE_RAW ~ byteSequence 34 | //! / NODE_TYPE_STRING ~ string 35 | //! / NODE_TYPE_BOOL ~ bool 36 | //! / NODE_TYPE_UNSIGNED ~ unsingedVarInt 37 | //! / NODE_TYPE_SIGNED ~ singedVarInt 38 | //! / NODE_TYPE_F32 ~ f32be 39 | //! / NODE_TYPE_F64 ~ f64be 40 | //! 41 | //! def LIST_TYPE_VERBATIM = 0:u8 42 | //! def LIST_TYPE_RLE = 1:u8 43 | //! 44 | //! // lists are homogenous, though you can store a NODE_TYPE_NODE to make the list effectively heterogeneous. 45 | //! def listNode = 46 | //! / LIST_TYPE_VERBATIM ~ verbatimListNode 47 | //! / LIST_TYPE_RLE ~ rleListNode 48 | //! 49 | //! def rleListNode = 50 | //! / NODE_TYPE_NODE ~ { !0:u8 ~ runLength:unsingedVarInt ~ node }* ~ 0:u8 51 | //! / NODE_TYPE_MAP ~ { !0:u8 ~ runLength:unsignedVarInt ~ mapNode }* ~ 0:u8 52 | //! / NODE_TYPE_LIST ~ { !0:u8 ~ runLength:unsignedVarInt ~ listNode }* ~ 0:u8 53 | //! / NODE_TYPE_RAW ~ { !0:u8 ~ runLength:unsignedVarInt ~ byteSequence }* ~ 0:u8 54 | //! / NODE_TYPE_STRING ~ { !0:u8 ~ runLength:unsignedVarInt ~ string }* ~ 0:u8 55 | //! / NODE_TYPE_BOOL ~ { !0:u8 ~ runLength:unsignedVarInt ~ bool }* ~ 0:u8 56 | //! / NODE_TYPE_UNSIGNED ~ { !0:u8 ~ runLength:unsignedVarInt ~ unsingedVarInt }* ~ 0:u8 57 | //! / NODE_TYPE_SIGNED ~ { !0:u8 ~ runLength:unsignedVarInt ~ singedVarInt }* ~ 0:u8 58 | //! / NODE_TYPE_F32 ~ { !0:u8 ~ runLength:unsignedVarInt ~ f32be }* ~ 0:u8 59 | //! / NODE_TYPE_F64 ~ { !0:u8 ~ runLength:unsignedVarInt ~ f64be }* ~ 0:u8 60 | //! 61 | //! def verbatimListNode = length:unsingedVarInt ~ { 62 | //! / NODE_TYPE_NODE ~ node{length} 63 | //! / NODE_TYPE_MAP ~ mapNode{length} 64 | //! / NODE_TYPE_LIST ~ listNode{length} 65 | //! / NODE_TYPE_RAW ~ byteSequence{length} 66 | //! / NODE_TYPE_STRING ~ string{length} 67 | //! / NODE_TYPE_BOOL ~ bool{length} 68 | //! / NODE_TYPE_UNSIGNED ~ unsingedVarInt{length} 69 | //! / NODE_TYPE_SIGNED ~ singedVarInt{length} 70 | //! / NODE_TYPE_F32 ~ f32be{length} 71 | //! / NODE_TYPE_F64 ~ f64be{length} 72 | //! } 73 | //! 74 | //! def mapNode = { !0:u8 ~ key:string ~ value:node }* ~ 0:u8 75 | //! ``` 76 | 77 | pub mod decode; 78 | pub mod encode; 79 | 80 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 81 | #[repr(u8)] 82 | pub enum NodeKind { 83 | /// "wrapper" node which only contains another node. 84 | Node = 0, 85 | Map = 1, 86 | List = 2, 87 | /// raw binary data 88 | Raw = 3, 89 | /// UTF-8 encoded string 90 | String = 4, 91 | Bool = 5, 92 | 93 | // VLQ-encoded integers 94 | UnsignedVarInt = 6, 95 | SignedVarInt = 7, 96 | 97 | // floating-point numbers 98 | Float32 = 8, 99 | Float64 = 9, 100 | } 101 | 102 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 103 | #[repr(u8)] 104 | pub enum ListKind { 105 | Verbatim = 0, 106 | RunLength = 1, 107 | } 108 | -------------------------------------------------------------------------------- /notcraft-common/src/debug.rs: -------------------------------------------------------------------------------- 1 | #[cfg(feature = "debug")] 2 | mod inner { 3 | use super::DebugEvent; 4 | use crate::util::ChannelPair; 5 | use std::{ 6 | any::{Any, TypeId}, 7 | collections::HashSet, 8 | sync::atomic::{AtomicBool, Ordering}, 9 | }; 10 | 11 | lazy_static::lazy_static! { 12 | static ref DEBUG_EVENTS: flurry::HashMap> = Default::default(); 13 | } 14 | 15 | pub trait DebugEventChannel: Any + Send + Sync + 'static { 16 | fn clear(&self); 17 | fn set_drained(&self); 18 | } 19 | 20 | impl dyn DebugEventChannel { 21 | fn downcast_ref(&self) -> Option<&T> { 22 | if self.type_id() == TypeId::of::() { 23 | Some(unsafe { &*(self as *const dyn DebugEventChannel as *const T) }) 24 | } else { 25 | None 26 | } 27 | } 28 | } 29 | 30 | struct DebugChannel { 31 | inner: ChannelPair, 32 | drained: AtomicBool, 33 | } 34 | 35 | impl DebugEventChannel for DebugChannel { 36 | fn clear(&self) { 37 | // the `self.drained` flag is needed because there might be events that come in 38 | // after the channel has been drained, but before the channels are cleared. we 39 | // want to clear a channel if nobody is listening to it, though, to avoid debug 40 | // events piling up. 41 | if !self.drained.swap(false, Ordering::SeqCst) { 42 | self.inner.rx.try_iter().for_each(drop); 43 | } 44 | } 45 | 46 | fn set_drained(&self) { 47 | self.drained.store(true, Ordering::SeqCst); 48 | } 49 | } 50 | 51 | pub fn register_debug_event(enabled: Option<&HashSet>) { 52 | enable_debug_event::(enabled.map_or(true, |set| set.contains(E::name()))); 53 | } 54 | 55 | pub fn enable_debug_event(enable: bool) { 56 | let id = TypeId::of::(); 57 | let channel = Box::new(DebugChannel { 58 | inner: ChannelPair::::default(), 59 | drained: AtomicBool::new(false), 60 | }) as Box; 61 | match enable { 62 | true => drop(DEBUG_EVENTS.pin().try_insert(id, channel.into())), 63 | false => drop(DEBUG_EVENTS.pin().remove(&id)), 64 | } 65 | } 66 | 67 | pub fn send_debug_event(event: E) { 68 | if let Some(channel) = DEBUG_EVENTS.pin().get(&TypeId::of::()) { 69 | let tx = &channel.downcast_ref::>().unwrap().inner.tx; 70 | tx.send(event).unwrap(); 71 | } 72 | } 73 | 74 | pub fn drain_debug_events(func: F) 75 | where 76 | F: FnMut(E), 77 | { 78 | if let Some(channel) = DEBUG_EVENTS.pin().get(&TypeId::of::()) { 79 | channel.set_drained(); 80 | let rx = &channel.downcast_ref::>().unwrap().inner.rx; 81 | rx.try_iter().for_each(func); 82 | } 83 | } 84 | 85 | pub fn clear_debug_events() { 86 | for value in DEBUG_EVENTS.pin().values() { 87 | value.clear(); 88 | } 89 | } 90 | } 91 | 92 | // use dummy implementations that do nothing when the debug feature is disabled, 93 | // so we can still call these functions unconditionally, for simplicity. 94 | #[cfg(not(feature = "debug"))] 95 | mod inner { 96 | use super::DebugEvent; 97 | use std::collections::HashSet; 98 | 99 | pub fn register_debug_event(_enabled: Option<&HashSet>) {} 100 | 101 | pub fn enable_debug_event(_enable: bool) {} 102 | 103 | pub fn send_debug_event(_event: E) {} 104 | 105 | pub fn drain_debug_events(_func: F) 106 | where 107 | F: FnMut(E), 108 | { 109 | } 110 | 111 | pub fn clear_debug_events() {} 112 | } 113 | 114 | pub use inner::*; 115 | 116 | pub trait DebugEvent: Send + Sync + 'static { 117 | fn name() -> &'static str; 118 | } 119 | 120 | #[macro_export] 121 | macro_rules! debug_events { 122 | ($modname:ident, $($type:path => $name:expr,)*) => { 123 | pub mod $modname { 124 | use super::*; 125 | 126 | $(impl $crate::debug::DebugEvent for $type { 127 | fn name() -> &'static str { 128 | $name 129 | } 130 | })* 131 | 132 | pub fn enumerate(enabled: Option<&std::collections::HashSet>) { 133 | $($crate::debug::register_debug_event::<$type>(enabled);)* 134 | } 135 | } 136 | }; 137 | } 138 | 139 | pub use debug_events; 140 | -------------------------------------------------------------------------------- /notcraft-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{vector, Vector3}; 2 | use num_traits::{One, Zero}; 3 | use serde::{Deserialize, Serialize}; 4 | use std::ops::Neg; 5 | 6 | pub mod aabb; 7 | pub mod codec; 8 | pub mod net; 9 | pub mod physics; 10 | pub mod transform; 11 | pub mod util; 12 | pub mod world; 13 | 14 | pub mod debug; 15 | 16 | pub mod math { 17 | pub use nalgebra::{Matrix3, Matrix4, Point1, Point2, Point3, Vector2, Vector3, Vector4}; 18 | } 19 | 20 | pub mod prelude { 21 | pub use super::util; 22 | 23 | pub use bevy_app::prelude::*; 24 | pub use bevy_core::prelude::*; 25 | pub use bevy_ecs::prelude::*; 26 | 27 | pub type Result = std::result::Result; 28 | pub use anyhow::{anyhow, bail}; 29 | 30 | pub use nalgebra::{point, vector}; 31 | } 32 | 33 | #[repr(u8)] 34 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 35 | pub enum Axis { 36 | X = 0, 37 | Y = 1, 38 | Z = 2, 39 | } 40 | 41 | /// Six sides of a cube. 42 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash)] 43 | pub enum Side { 44 | /// Positive Y. 45 | Top, 46 | /// Negative Y. 47 | Bottom, 48 | /// Positive X. 49 | Right, 50 | /// Negative X. 51 | Left, 52 | /// Positive Z. 53 | Front, 54 | /// Negative Z. 55 | Back, 56 | } 57 | 58 | impl Side { 59 | pub fn facing_positive(&self) -> bool { 60 | match self { 61 | Side::Top | Side::Right | Side::Front => true, 62 | _ => false, 63 | } 64 | } 65 | 66 | pub fn normal>(&self) -> Vector3 { 67 | match *self { 68 | Side::Top => vector!(S::zero(), S::one(), S::zero()), 69 | Side::Bottom => vector!(S::zero(), -S::one(), S::zero()), 70 | Side::Right => vector!(S::one(), S::zero(), S::zero()), 71 | Side::Left => vector!(-S::one(), S::zero(), S::zero()), 72 | Side::Front => vector!(S::zero(), S::zero(), S::one()), 73 | Side::Back => vector!(S::zero(), S::zero(), -S::one()), 74 | } 75 | } 76 | 77 | pub fn axis(&self) -> Axis { 78 | match self { 79 | Side::Left | Side::Right => Axis::X, 80 | Side::Top | Side::Bottom => Axis::Y, 81 | Side::Front | Side::Back => Axis::Z, 82 | } 83 | } 84 | 85 | pub fn enumerate(mut func: F) 86 | where 87 | F: FnMut(Side), 88 | { 89 | func(Side::Right); 90 | func(Side::Left); 91 | func(Side::Top); 92 | func(Side::Bottom); 93 | func(Side::Front); 94 | func(Side::Back); 95 | } 96 | 97 | /// take coordinates (u, v, l) where (u, v) is parallel to this face and 98 | /// convert it to a relative xyz coord 99 | pub fn uvl_to_xyz>( 100 | &self, 101 | u: S, 102 | v: S, 103 | l: S, 104 | ) -> Vector3 { 105 | let axis = self.axis(); 106 | let l = [-l, l][self.facing_positive() as usize]; 107 | 108 | let mut vec = vector![S::zero(), S::zero(), S::zero()]; 109 | vec[axis as usize % 3] = l; 110 | vec[(axis as usize + 1) % 3] = u; 111 | vec[(axis as usize + 2) % 3] = v; 112 | vec 113 | } 114 | } 115 | 116 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default, Serialize, Deserialize)] 117 | #[serde(rename_all = "kebab-case")] 118 | pub struct Faces { 119 | pub top: T, 120 | pub bottom: T, 121 | pub right: T, 122 | pub left: T, 123 | pub front: T, 124 | pub back: T, 125 | } 126 | 127 | impl Faces { 128 | pub fn map(self, mut func: F) -> Faces 129 | where 130 | F: FnMut(T) -> U, 131 | { 132 | Faces { 133 | top: func(self.top), 134 | bottom: func(self.bottom), 135 | left: func(self.left), 136 | right: func(self.right), 137 | front: func(self.front), 138 | back: func(self.back), 139 | } 140 | } 141 | 142 | pub fn all(&self, mut func: F) -> bool 143 | where 144 | F: FnMut(&T) -> bool, 145 | { 146 | func(&self.top) 147 | && func(&self.bottom) 148 | && func(&self.left) 149 | && func(&self.right) 150 | && func(&self.front) 151 | && func(&self.back) 152 | } 153 | 154 | pub fn any(&self, mut func: F) -> bool 155 | where 156 | F: FnMut(&T) -> bool, 157 | { 158 | func(&self.top) 159 | || func(&self.bottom) 160 | || func(&self.left) 161 | || func(&self.right) 162 | || func(&self.front) 163 | || func(&self.back) 164 | } 165 | } 166 | 167 | impl std::ops::Index for Faces { 168 | type Output = T; 169 | 170 | fn index(&self, index: Side) -> &Self::Output { 171 | match index { 172 | Side::Top => &self.top, 173 | Side::Bottom => &self.bottom, 174 | Side::Right => &self.right, 175 | Side::Left => &self.left, 176 | Side::Front => &self.front, 177 | Side::Back => &self.back, 178 | } 179 | } 180 | } 181 | -------------------------------------------------------------------------------- /notcraft-common/src/net/mod.rs: -------------------------------------------------------------------------------- 1 | // pub mod packet; 2 | -------------------------------------------------------------------------------- /notcraft-common/src/net/packet/mod.rs: -------------------------------------------------------------------------------- 1 | #[derive(Clone, Debug)] 2 | pub enum ClientToServerLoginPacket { 3 | Login { username: String }, 4 | } 5 | 6 | #[derive(Clone, Debug)] 7 | pub enum ServerToClientLoginPacket { 8 | LoginAck { 9 | initial_position: Point3, 10 | initial_pitch: f32, 11 | initial_yaw: f32, 12 | }, 13 | } 14 | 15 | #[derive(Clone, Debug)] 16 | pub enum ClientToServerPlayPacket { 17 | UpdateTransform { 18 | position: Point3, 19 | pitch: f32, 20 | yaw: f32, 21 | }, 22 | BreakBlock { 23 | position: BlockPos, 24 | }, 25 | PlaceBlock { 26 | id: BlockId, 27 | position: BlockPos, 28 | }, 29 | } 30 | 31 | #[derive(Clone, Debug)] 32 | pub enum ServerToClientPlayPacket { 33 | ChunkData { data: Box<[BlockId]> }, 34 | } 35 | -------------------------------------------------------------------------------- /notcraft-common/src/transform.rs: -------------------------------------------------------------------------------- 1 | use nalgebra::{vector, Matrix4, Point3, Translation3, UnitQuaternion, Vector3}; 2 | 3 | #[derive(Copy, Clone, Debug, PartialEq)] 4 | pub struct EulerAngles { 5 | pub pitch: f32, 6 | pub yaw: f32, 7 | pub roll: f32, 8 | } 9 | 10 | impl EulerAngles { 11 | pub fn new(pitch: f32, yaw: f32, roll: f32) -> Self { 12 | Self { pitch, yaw, roll } 13 | } 14 | 15 | pub fn to_quaternion(&self) -> UnitQuaternion { 16 | UnitQuaternion::from_euler_angles(self.pitch, self.yaw, self.roll) 17 | } 18 | } 19 | 20 | #[derive(Copy, Clone, Debug, PartialEq)] 21 | pub struct Transform { 22 | pub translation: Translation3, 23 | pub rotation: EulerAngles, 24 | pub scale: Vector3, 25 | } 26 | 27 | impl Transform { 28 | pub fn to(point: Point3) -> Self { 29 | Self { 30 | translation: Translation3::from(point), 31 | ..Default::default() 32 | } 33 | } 34 | 35 | pub fn translate_local(&mut self, translation: Vector3) { 36 | let transformed_translation = self.rotation.to_quaternion() * translation; 37 | self.translation.vector += transformed_translation; 38 | } 39 | 40 | pub fn translate_global(&mut self, translation: Vector3) { 41 | self.translation.vector += translation.component_mul(&self.scale); 42 | } 43 | 44 | pub fn translated(&self, translation: &Vector3) -> Transform { 45 | Transform { 46 | translation: Translation3::from( 47 | self.translation.vector + translation.component_mul(&self.scale), 48 | ), 49 | ..*self 50 | } 51 | } 52 | 53 | pub fn pos(&self) -> Point3 { 54 | self.translation.vector.into() 55 | } 56 | 57 | pub fn to_matrix(&self) -> Matrix4 { 58 | // The model/world matrix takes points in local space and converts them to world 59 | // space. 60 | self.rotation 61 | .to_quaternion() 62 | .to_homogeneous() 63 | .append_translation(&self.translation.vector) 64 | .prepend_nonuniform_scaling(&self.scale) 65 | } 66 | } 67 | 68 | impl Default for Transform { 69 | fn default() -> Self { 70 | Transform { 71 | translation: Translation3::from(vector!(0.0, 0.0, 0.0)), 72 | rotation: EulerAngles::new(0.0, 0.0, 0.0), 73 | scale: vector!(1.0, 1.0, 1.0), 74 | } 75 | } 76 | } 77 | 78 | impl From> for Transform { 79 | fn from(point: Point3) -> Self { 80 | Transform { 81 | translation: Translation3::from(point.coords), 82 | rotation: EulerAngles::new(0.0, 0.0, 0.0), 83 | scale: vector!(1.0, 1.0, 1.0), 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /notcraft-common/src/util.rs: -------------------------------------------------------------------------------- 1 | use crate::{aabb::Aabb, world::BlockPos}; 2 | use bevy_app::{AppExit, EventWriter}; 3 | use bevy_ecs::prelude::In; 4 | use nalgebra::{point, vector, Point3, Vector3}; 5 | use std::{cmp::Ordering, fmt::Display}; 6 | 7 | #[inline(always)] 8 | pub fn invlerp(a: f32, b: f32, n: f32) -> f32 { 9 | // you can get this by solving for `t` in the equation for `lerp` 10 | (n - a) / (b - a) 11 | } 12 | 13 | #[inline(always)] 14 | pub fn lerp(a: f32, b: f32, t: f32) -> f32 { 15 | a * (1.0 - t) + b * t 16 | } 17 | 18 | #[inline(always)] 19 | pub fn remap(input_start: f32, input_end: f32, output_start: f32, output_end: f32, n: f32) -> f32 { 20 | lerp(output_start, output_end, invlerp(input_start, input_end, n)) 21 | } 22 | 23 | #[cfg(test)] 24 | mod tests { 25 | use approx::assert_relative_eq; 26 | 27 | use super::*; 28 | 29 | #[test] 30 | fn test_remap() { 31 | assert_relative_eq!(remap(0.0, 1.0, 0.0, 1.0, 0.5), 0.5); 32 | assert_relative_eq!(remap(0.0, 2.0, 0.0, 1.0, 0.5), 0.25); 33 | assert_relative_eq!(remap(0.0, 1.0, 0.0, 2.0, 0.5), 1.0); 34 | assert_relative_eq!(remap(0.0, 2.0, 0.0, 2.0, 0.5), 0.5); 35 | } 36 | } 37 | 38 | #[inline(always)] 39 | pub fn lerp_vec(a: Vector3, b: Vector3, t: f32) -> Vector3 { 40 | vector![lerp(a.x, b.x, t), lerp(a.y, b.y, t), lerp(a.z, b.z, t)] 41 | } 42 | 43 | #[inline(always)] 44 | pub fn lerp_point(a: Point3, b: Point3, t: f32) -> Point3 { 45 | point![lerp(a.x, b.x, t), lerp(a.y, b.y, t), lerp(a.z, b.z, t)] 46 | } 47 | 48 | /// Version of `min` that only requires `PartialOrd` 49 | #[inline(always)] 50 | pub fn min(lhs: S, rhs: S) -> S { 51 | match lhs.partial_cmp(&rhs) { 52 | Some(Ordering::Less) | Some(Ordering::Equal) | None => lhs, 53 | _ => rhs, 54 | } 55 | } 56 | 57 | /// Version of `max` that only requires `PartialOrd` 58 | #[inline(always)] 59 | pub fn max(lhs: S, rhs: S) -> S { 60 | match lhs.partial_cmp(&rhs) { 61 | Some(Ordering::Greater) | Some(Ordering::Equal) | None => lhs, 62 | _ => rhs, 63 | } 64 | } 65 | 66 | /// Limits the range of `x` to be within `[a, b]` 67 | #[inline(always)] 68 | pub fn clamp(a: T, b: T, x: T) -> T { 69 | if x < a { 70 | a 71 | } else if x > b { 72 | b 73 | } else { 74 | x 75 | } 76 | } 77 | 78 | #[inline(always)] 79 | pub fn is_within(t: T, min: T, max: T) -> bool { 80 | t >= min && t <= max 81 | } 82 | 83 | #[inline(always)] 84 | pub fn is_between(t: T, min: T, max: T) -> bool { 85 | t > min && t < max 86 | } 87 | 88 | /// x / y, round towards negative inf 89 | #[inline(always)] 90 | pub fn floor_div(x: i32, y: i32) -> i32 { 91 | let result = x / y; 92 | let remainder = x % y; 93 | if remainder < 0 { 94 | result - 1 95 | } else { 96 | result 97 | } 98 | } 99 | 100 | /// Tests if `pos` is within `r` units from `center` 101 | pub fn in_range(pos: Point3, center: Point3, radii: Vector3) -> bool { 102 | pos.x <= center.x + radii.x 103 | && pos.x >= center.x - radii.x 104 | && pos.y <= center.y + radii.y 105 | && pos.y >= center.y - radii.y 106 | && pos.z <= center.z + radii.z 107 | && pos.z >= center.z - radii.z 108 | } 109 | 110 | /// Mathematical mod function 111 | #[inline(always)] 112 | pub fn modulo(a: f32, b: f32) -> f32 { 113 | (a % b + b) % b 114 | } 115 | 116 | pub struct Defer(pub Option); 117 | impl Drop for Defer { 118 | fn drop(&mut self) { 119 | (self.0.take().unwrap())(); 120 | } 121 | } 122 | 123 | #[macro_export] 124 | macro_rules! defer { 125 | ($($code:tt)*) => { 126 | let _defer = $crate::util::Defer(Some(|| drop({ $($code)* }))); 127 | }; 128 | } 129 | 130 | pub use defer; 131 | 132 | #[derive(Debug)] 133 | pub struct ChannelPair { 134 | pub rx: crossbeam_channel::Receiver, 135 | pub tx: crossbeam_channel::Sender, 136 | } 137 | 138 | impl Default for ChannelPair { 139 | fn default() -> Self { 140 | let (tx, rx) = crossbeam_channel::unbounded(); 141 | Self { rx, tx } 142 | } 143 | } 144 | 145 | impl ChannelPair { 146 | pub fn sender(&self) -> crossbeam_channel::Sender { 147 | self.tx.clone() 148 | } 149 | } 150 | 151 | pub fn block_aabb(block: BlockPos) -> Aabb { 152 | let pos = point![block.x as f32, block.y as f32, block.z as f32]; 153 | Aabb { 154 | min: pos, 155 | max: pos + vector![1.0, 1.0, 1.0], 156 | } 157 | } 158 | 159 | pub fn handle_error_internal(In(res): In>, mut exit: EventWriter) 160 | where 161 | E: Display, 162 | { 163 | match res { 164 | Ok(_) => {} 165 | Err(err) => { 166 | log::error!("{}", err); 167 | exit.send(AppExit); 168 | } 169 | } 170 | } 171 | 172 | #[macro_export] 173 | macro_rules! try_system { 174 | ($sys:expr) => { 175 | $sys.system() 176 | .chain($crate::util::handle_error_internal.system()) 177 | }; 178 | } 179 | 180 | pub use try_system; 181 | -------------------------------------------------------------------------------- /notcraft-common/src/world/generation/mod.rs: -------------------------------------------------------------------------------- 1 | use self::spline::Spline; 2 | 3 | use super::{ 4 | chunk::ChunkData, 5 | registry::{BlockId, BlockRegistry, AIR_BLOCK}, 6 | BlockPos, ChunkPos, ChunkSectionPos, 7 | }; 8 | use crate::{ 9 | codec::{ 10 | encode::{Encode, Encoder}, 11 | NodeKind, 12 | }, 13 | prelude::*, 14 | world::chunk::{CHUNK_LENGTH, CHUNK_LENGTH_2, CHUNK_LENGTH_3}, 15 | }; 16 | use noise::{Fbm, MultiFractal, NoiseFn, OpenSimplex, Perlin}; 17 | use rand::{rngs::SmallRng, FromEntropy, Rng, SeedableRng}; 18 | use std::{ 19 | collections::hash_map::DefaultHasher, 20 | sync::{ 21 | atomic::{AtomicU64, Ordering}, 22 | Arc, 23 | }, 24 | time::{Duration, Instant}, 25 | }; 26 | 27 | pub mod spline; 28 | 29 | #[derive(Clone, Debug)] 30 | pub struct SurfaceHeightmap { 31 | min: i32, 32 | max: i32, 33 | timestamp: Arc, 34 | data: Arc<[i32]>, 35 | } 36 | 37 | impl SurfaceHeightmap { 38 | pub fn data(&self) -> &Arc<[i32]> { 39 | &self.data 40 | } 41 | } 42 | 43 | #[derive(Debug)] 44 | pub struct SurfaceHeighmapCache { 45 | time_reference: Instant, 46 | heightmaps: flurry::HashMap, 47 | } 48 | 49 | impl Default for SurfaceHeighmapCache { 50 | fn default() -> Self { 51 | Self { 52 | time_reference: Instant::now(), 53 | heightmaps: Default::default(), 54 | } 55 | } 56 | } 57 | 58 | fn generate_surface_heights( 59 | cache: &SurfaceHeighmapCache, 60 | seed: u64, 61 | pos: ChunkPos, 62 | shaping_curve: &Spline, 63 | ) -> SurfaceHeightmap { 64 | // let mix_noise = NoiseSampler::seeded(seed, Perlin::new()).with_scale(0.0001); 65 | // let rolling_noise = NoiseSampler::seeded(seed, 66 | // Perlin::new()).with_scale(0.0003); 67 | 68 | // let mountainous_noise_unwarped = NoiseSampler::seeded(seed, 69 | // Fbm::new()).with_scale(0.002); let mountainous_noise = 70 | // NoiseSampler::seeded(seed, Fbm::new()).with_scale(0.001); 71 | // let warp_noise_x = NoiseSampler::seeded(seed, Perlin::new()) 72 | // .with_offset([0.0, 0.5]) 73 | // .with_scale(0.003); 74 | // let warp_noise_z = NoiseSampler::seeded(seed, Perlin::new()) 75 | // .with_offset([0.5, 0.0]) 76 | // .with_scale(0.003); 77 | let noise = NoiseSamplerN::seeded(seed, Fbm::new().set_octaves(4)).with_scale(0.004); 78 | 79 | let mut min = i32::MAX; 80 | let mut max = i32::MIN; 81 | 82 | let mut heights = Vec::with_capacity(CHUNK_LENGTH_2); 83 | for dx in 0..CHUNK_LENGTH { 84 | for dz in 0..CHUNK_LENGTH { 85 | let (x, z) = ( 86 | CHUNK_LENGTH as f32 * pos.x as f32 + dx as f32, 87 | CHUNK_LENGTH as f32 * pos.z as f32 + dz as f32, 88 | ); 89 | 90 | // let wx = x + 200.0 * warp_noise_x.sample(x, z); 91 | // let wz = z + 200.0 * warp_noise_z.sample(x, z); 92 | 93 | // let warped = 2000.0 * (mountainous_noise.sample(wx, wz) * 0.5 + 0.5); 94 | // let mountain = 800.0 * (mountainous_noise_unwarped.sample(x, z) * 0.5 + 0.5); 95 | // let rolling = 100.0 * rolling_noise.sample(x, z); 96 | // let result = rolling + mix_noise.sample(x, z) * (warped + mountain); 97 | let result = shaping_curve.sample(noise.sample([x, z])); 98 | 99 | // let result = 100.0 * f32::sin(x / 30.0) * f32::cos(z / 30.0); 100 | 101 | let result = result.floor() as i32; 102 | 103 | min = i32::min(min, result); 104 | max = i32::max(max, result); 105 | 106 | heights.push(result); 107 | } 108 | } 109 | 110 | SurfaceHeightmap { 111 | min, 112 | max, 113 | timestamp: Arc::new(cache.timestamp().into()), 114 | data: heights.into_boxed_slice().into(), 115 | } 116 | } 117 | 118 | impl SurfaceHeighmapCache { 119 | pub fn surface_heights( 120 | &self, 121 | seed: u64, 122 | shaping_curve: &Spline, 123 | pos: ChunkPos, 124 | ) -> SurfaceHeightmap { 125 | if let Some(cached) = self.heightmaps.pin().get(&pos) { 126 | cached.timestamp.store(self.timestamp(), Ordering::SeqCst); 127 | return SurfaceHeightmap::clone(cached); 128 | } else { 129 | let surface_heights = generate_surface_heights(self, seed, pos, shaping_curve); 130 | self.heightmaps.pin().insert(pos, surface_heights); 131 | self.surface_heights(seed, shaping_curve, pos) 132 | } 133 | } 134 | 135 | fn timestamp(&self) -> u64 { 136 | self.time_reference.elapsed().as_secs() 137 | } 138 | 139 | pub fn evict_after(&self, delay: Duration) { 140 | self.heightmaps.pin().retain(|_, val| { 141 | self.timestamp() - val.timestamp.load(Ordering::SeqCst) > delay.as_secs() 142 | }); 143 | } 144 | } 145 | 146 | struct NoiseSamplerN { 147 | noise: F, 148 | offset: [f32; D], 149 | scale: f32, 150 | } 151 | 152 | impl NoiseSamplerN 153 | where 154 | F: noise::Seedable, 155 | { 156 | pub fn seeded(seed: u64, noise: F) -> Self { 157 | Self { 158 | noise: noise.set_seed(seed as u32), 159 | offset: [0.0; D], 160 | scale: 1.0, 161 | } 162 | } 163 | } 164 | 165 | impl NoiseSamplerN { 166 | pub fn sample_block(&self, pos: BlockPos) -> f32 167 | where 168 | F: NoiseFn<[f64; 3]>, 169 | { 170 | self.sample([pos.x as f32, pos.y as f32, pos.z as f32]) 171 | } 172 | } 173 | 174 | impl NoiseSamplerN { 175 | pub fn with_scale(mut self, scale: f32) -> Self { 176 | self.scale = scale; 177 | self 178 | } 179 | 180 | pub fn with_offset>(mut self, offset: I) -> Self { 181 | self.offset = offset.into(); 182 | self 183 | } 184 | 185 | pub fn sample(&self, pos: I) -> f32 186 | where 187 | [f32; D]: From, 188 | F: NoiseFn<[f64; D]>, 189 | { 190 | let mut pos = <[f32; D]>::from(pos); 191 | for i in 0..D { 192 | pos[i] = (self.offset[i] + pos[i]) * self.scale; 193 | } 194 | self.noise.get(pos.map(|elem| elem as f64)) as f32 195 | } 196 | } 197 | 198 | impl From for NoiseSamplerN 199 | where 200 | F: NoiseFn<[f64; D]>, 201 | { 202 | fn from(noise: F) -> Self { 203 | Self { 204 | noise, 205 | offset: [0.0; D], 206 | scale: 1.0, 207 | } 208 | } 209 | } 210 | 211 | #[derive(Debug)] 212 | pub struct ChunkGenerator { 213 | stone_id: BlockId, 214 | dirt_id: BlockId, 215 | grass_id: BlockId, 216 | water_id: BlockId, 217 | sand_id: BlockId, 218 | detail_grass_id: BlockId, 219 | } 220 | 221 | impl ChunkGenerator { 222 | pub fn new_default(registry: &BlockRegistry) -> Self { 223 | Self { 224 | stone_id: registry.lookup("stone"), 225 | dirt_id: registry.lookup("dirt"), 226 | grass_id: registry.lookup("grass"), 227 | water_id: registry.lookup("water"), 228 | sand_id: registry.lookup("sand"), 229 | detail_grass_id: registry.lookup("detail_grass"), 230 | } 231 | } 232 | 233 | fn pick_block>( 234 | &self, 235 | rng: &mut SmallRng, 236 | open_noise: &NoiseSamplerN, 237 | stringy_noise: &NoiseSamplerN, 238 | pos: BlockPos, 239 | surface: i32, 240 | ) -> BlockId { 241 | let distance = pos.y - surface; 242 | if distance < -20 && { 243 | let d1 = open_noise.sample_block(pos); 244 | let d2 = stringy_noise.sample_block(pos); 245 | 246 | d1.abs() < 0.05 && d2.abs() < 0.05 247 | 248 | // let density = stringy_noise.sample_block(pos); 249 | // let stringy_bias = util::clamp( 250 | // 0.0, 251 | // 1.0, 252 | // util::remap(-1.0, 1.0, -1.5, 3.0, 253 | // open_noise.sample_block(pos)), ); 254 | // let distance_bias = util::clamp(0.0, 1.0, -distance as f32 / 255 | // 100.0); 256 | 257 | // let density = util::lerp(1.0, density, distance_bias * 258 | // stringy_bias); density.abs() < 0.02 259 | } { 260 | AIR_BLOCK 261 | } else if distance < 0 { 262 | self.stone_id 263 | } else { 264 | AIR_BLOCK 265 | } 266 | // if distance < -4 { 267 | // self.stone_id 268 | // } else if distance < -1 { 269 | // self.dirt_id 270 | // } else if distance < 0 { 271 | // if y < CHUNK_LENGTH as i32 + 3 { 272 | // self.sand_id 273 | // } else { 274 | // self.grass_id 275 | // } 276 | // } else if y < CHUNK_LENGTH as i32 { 277 | // self.water_id 278 | // } else if distance < 1 { 279 | // if rng.gen_bool(1.0 / 3.0) { 280 | // self.detail_grass_id 281 | // } else { 282 | // AIR_BLOCK 283 | // } 284 | // } else { 285 | // AIR_BLOCK 286 | // } 287 | } 288 | 289 | pub fn make_chunk( 290 | &self, 291 | seed: u64, 292 | pos: ChunkSectionPos, 293 | heights: &SurfaceHeightmap, 294 | ) -> ChunkData { 295 | let base_x = pos.origin().x; 296 | let base_y = pos.origin().y; 297 | let base_z = pos.origin().z; 298 | 299 | let stringy_noise = NoiseSamplerN::seeded(seed, OpenSimplex::new()).with_scale(0.015); 300 | let open_noise = NoiseSamplerN::seeded(seed + 3, OpenSimplex::new()).with_scale(0.015); 301 | 302 | let seed = make_chunk_section_seed(seed, pos); 303 | let mut rng = SmallRng::seed_from_u64(seed); 304 | 305 | if base_y > heights.max { 306 | // if pos.y < 1 { 307 | // return ChunkData::Homogeneous(self.water_id); 308 | // } else { 309 | // } 310 | return ChunkData::Homogeneous(AIR_BLOCK); 311 | } 312 | // else if (base_y + CHUNK_LENGTH as i32) < heights.min { 313 | // return ChunkData::Homogeneous(self.stone_id); 314 | // } 315 | 316 | let mut chunk_data = Vec::with_capacity(CHUNK_LENGTH_3); 317 | for x in 0..CHUNK_LENGTH { 318 | for z in 0..CHUNK_LENGTH { 319 | let surface_height = heights.data[CHUNK_LENGTH * x + z]; 320 | chunk_data.extend((0..CHUNK_LENGTH).map(|y| { 321 | self.pick_block( 322 | &mut rng, 323 | &open_noise, 324 | &stringy_noise, 325 | BlockPos { 326 | x: base_x + x as i32, 327 | y: base_y + y as i32, 328 | z: base_z + z as i32, 329 | }, 330 | surface_height, 331 | ) 332 | })); 333 | } 334 | } 335 | 336 | assert!(!chunk_data.is_empty()); 337 | ChunkData::Array(chunk_data.into_boxed_slice().try_into().unwrap()) 338 | } 339 | } 340 | 341 | impl Encode for SurfaceHeightmap { 342 | const KIND: NodeKind = NodeKind::List; 343 | 344 | fn encode(&self, encoder: Encoder) -> Result<()> { 345 | encoder.encode_rle_list(self.data.iter().copied()) 346 | } 347 | } 348 | 349 | fn make_chunk_section_seed(world_seed: u64, chunk: ChunkSectionPos) -> u64 { 350 | let mut rng = SmallRng::seed_from_u64(world_seed); 351 | let x = rng.gen::() ^ SmallRng::seed_from_u64(chunk.x as u64).gen::(); 352 | let y = rng.gen::() ^ SmallRng::seed_from_u64(chunk.x as u64).gen::(); 353 | let z = rng.gen::() ^ SmallRng::seed_from_u64(chunk.z as u64).gen::(); 354 | x ^ y ^ z 355 | } 356 | 357 | fn make_chunk_seed(world_seed: u64, chunk: ChunkPos) -> u64 { 358 | let mut rng = SmallRng::seed_from_u64(world_seed); 359 | let x = rng.gen::() ^ SmallRng::seed_from_u64(chunk.x as u64).gen::(); 360 | let z = rng.gen::() ^ SmallRng::seed_from_u64(chunk.z as u64).gen::(); 361 | x ^ z 362 | } 363 | -------------------------------------------------------------------------------- /notcraft-common/src/world/generation/spline.rs: -------------------------------------------------------------------------------- 1 | use crate::util; 2 | 3 | #[derive(Clone, Debug, PartialEq, Default)] 4 | pub struct Spline { 5 | points: Vec, 6 | } 7 | 8 | #[derive(Clone, Debug, PartialEq)] 9 | pub struct SplinePoint { 10 | pub start: f32, 11 | pub height: f32, 12 | } 13 | 14 | impl Spline { 15 | pub fn with_point(mut self, point: SplinePoint) -> Self { 16 | match self 17 | .points 18 | .binary_search_by(|cur| PartialOrd::partial_cmp(&cur.start, &point.start).unwrap()) 19 | { 20 | Ok(idx) => self.points.insert(idx + 1, point), 21 | Err(idx) => self.points.insert(idx, point), 22 | } 23 | self 24 | } 25 | 26 | pub fn sample(&self, value: f32) -> f32 { 27 | match self 28 | .points 29 | .binary_search_by(|cur| PartialOrd::partial_cmp(&cur.start, &value).unwrap()) 30 | { 31 | // out of bounds of this sampler; just define everything outside to be the values of the 32 | // respective endpoints. 33 | Err(0) => self.points[0].height, 34 | Err(idx) if idx == self.points.len() => self.points[idx - 1].height, 35 | 36 | Ok(idx) => self.points[idx].height, 37 | Err(idx) => { 38 | assert!(self.points[idx - 1].start <= value); 39 | assert!(self.points[idx].start >= value); 40 | util::remap( 41 | self.points[idx - 1].start, 42 | self.points[idx].start, 43 | self.points[idx - 1].height, 44 | self.points[idx].height, 45 | value, 46 | ) 47 | } 48 | } 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /notcraft-common/src/world/lighting.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cmp::Ordering, 3 | collections::{BTreeMap, HashSet, VecDeque}, 4 | ops::Bound, 5 | }; 6 | 7 | use super::{ 8 | chunk::{MutableChunkAccess, CHUNK_LENGTH, CHUNK_LENGTH_2}, 9 | generation::SurfaceHeightmap, 10 | BlockPos, 11 | }; 12 | use crate::{ 13 | codec::{ 14 | encode::{Encode, Encoder}, 15 | NodeKind, 16 | }, 17 | prelude::*, 18 | }; 19 | 20 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] 21 | #[repr(transparent)] 22 | pub struct LightValue(pub u16); 23 | 24 | impl Encode for LightValue { 25 | const KIND: NodeKind = NodeKind::UnsignedVarInt; 26 | 27 | fn encode(&self, encoder: Encoder) -> Result<()> { 28 | encoder.encode(&self.0) 29 | } 30 | } 31 | 32 | pub const SKY_LIGHT_BITS: u16 = 4; 33 | pub const BLOCK_LIGHT_BITS: u16 = 4; 34 | 35 | pub const SKY_LIGHT_MASK: u16 = ((1 << SKY_LIGHT_BITS) - 1) << BLOCK_LIGHT_BITS; 36 | pub const BLOCK_LIGHT_MASK: u16 = (1 << BLOCK_LIGHT_BITS) - 1; 37 | 38 | pub const FULL_SKY_LIGHT: LightValue = LightValue::pack(15, 0); 39 | 40 | impl LightValue { 41 | pub const fn new(value: u16) -> Self { 42 | Self(value) 43 | } 44 | 45 | pub const fn pack(sky: u16, block: u16) -> Self { 46 | let mut val = 0; 47 | val |= block & BLOCK_LIGHT_MASK; 48 | val |= (sky << BLOCK_LIGHT_BITS) & SKY_LIGHT_MASK; 49 | Self(val) 50 | } 51 | 52 | pub const fn raw(self) -> u16 { 53 | self.0 54 | } 55 | 56 | pub const fn sky(self) -> u16 { 57 | self.0 >> BLOCK_LIGHT_BITS 58 | } 59 | 60 | pub const fn block(self) -> u16 { 61 | self.0 & BLOCK_LIGHT_MASK 62 | } 63 | 64 | pub const fn intensity(self) -> u16 { 65 | if self.sky() > self.block() { 66 | self.sky() 67 | } else { 68 | self.block() 69 | } 70 | } 71 | 72 | pub fn combine_max(self, other: LightValue) -> LightValue { 73 | let block = u16::max(self.0 & BLOCK_LIGHT_MASK, other.0 & BLOCK_LIGHT_MASK); 74 | let sky = u16::max(self.0 & SKY_LIGHT_MASK, other.0 & SKY_LIGHT_MASK); 75 | LightValue(sky | block) 76 | } 77 | } 78 | 79 | // the basic idea for this comes from the Seed of Andromeda light update code. 80 | // there used to be a technical blog post about it on their site, but that has 81 | // since gone defunct 82 | // 83 | // it should not matter what order we tackle propagation in, since any low 84 | // values we set will get overwritten with a higher value later if need be. 85 | // 86 | // SoA lighting code: https://github.com/RegrowthStudios/SoACode-Public/blob/develop/SoA/VoxelLightEngine.cpp 87 | #[derive(Debug, Default)] 88 | pub struct LightUpdateQueues { 89 | block_removal: VecDeque<(BlockPos, u16)>, 90 | block_update: VecDeque<(BlockPos, u16)>, 91 | sky_removal: VecDeque<(BlockPos, u16)>, 92 | sky_update: VecDeque<(BlockPos, u16)>, 93 | visited: HashSet, 94 | } 95 | 96 | impl LightUpdateQueues { 97 | pub fn queue_blocklight_updates(&mut self, access: &mut MutableChunkAccess, iter: I) 98 | where 99 | I: Iterator, 100 | { 101 | for (pos, new_light) in iter { 102 | if !self.visited.insert(pos) { 103 | continue; 104 | } 105 | 106 | let prev_light = access.light(pos).unwrap(); 107 | 108 | let id = access.block(pos).unwrap(); 109 | if access.registry().get(id).light_transmissible() { 110 | self.sky_removal.push_back((pos, prev_light.sky())); 111 | self.block_removal.push_back((pos, prev_light.block())); 112 | } 113 | 114 | match new_light.cmp(&prev_light.block()) { 115 | Ordering::Equal => {} 116 | Ordering::Less => self.block_removal.push_back((pos, prev_light.block())), 117 | Ordering::Greater => self.block_update.push_back((pos, new_light)), 118 | } 119 | } 120 | 121 | self.visited.clear(); 122 | } 123 | 124 | pub fn queue_skylight_updates( 125 | &mut self, 126 | access: &mut MutableChunkAccess, 127 | x: i32, 128 | z: i32, 129 | min_y: i32, 130 | max_y: i32, // exclusive bound 131 | light: u16, 132 | ) { 133 | // log::debug!( 134 | // "queued skylight updates: ({x},{z}) -> min={min_y}, max={max_y}, 135 | // light={light}" ); 136 | for y in min_y..max_y { 137 | let pos = BlockPos { x, y, z }; 138 | 139 | let prev_light = access.light(pos).unwrap().sky(); 140 | 141 | match light.cmp(&prev_light) { 142 | Ordering::Equal => {} 143 | Ordering::Less => self.sky_removal.push_back((pos, prev_light)), 144 | Ordering::Greater => self.sky_update.push_back((pos, light)), 145 | } 146 | } 147 | } 148 | } 149 | 150 | pub(crate) fn propagate_block_light( 151 | queues: &mut LightUpdateQueues, 152 | access: &mut MutableChunkAccess, 153 | ) { 154 | for &(pos, _) in queues.block_removal.iter() { 155 | access.set_block_light(pos, 0).unwrap(); 156 | } 157 | 158 | while let Some((pos, light)) = queues.block_removal.pop_front() { 159 | let dirs = [ 160 | pos.offset([1, 0, 0]), 161 | pos.offset([-1, 0, 0]), 162 | pos.offset([0, 1, 0]), 163 | pos.offset([0, -1, 0]), 164 | pos.offset([0, 0, 1]), 165 | pos.offset([0, 0, -1]), 166 | ]; 167 | 168 | for dir in dirs.into_iter() { 169 | let neighbor_light = access.light(dir).unwrap().block(); 170 | 171 | if neighbor_light > 0 && neighbor_light < light { 172 | access.set_block_light(dir, 0).unwrap(); 173 | queues.block_removal.push_back((dir, light - 1)); 174 | } else if neighbor_light > 0 { 175 | queues.block_update.push_back((dir, 0)); 176 | } 177 | } 178 | } 179 | 180 | while let Some((pos, queue_light)) = queues.block_update.pop_front() { 181 | let current_light = access.light(pos).unwrap().block(); 182 | let queue_light = u16::max(queue_light, current_light); 183 | if queue_light != current_light { 184 | access.set_block_light(pos, queue_light).unwrap(); 185 | } 186 | 187 | if queue_light == 0 { 188 | continue; 189 | } 190 | 191 | let dirs = [ 192 | pos.offset([1, 0, 0]), 193 | pos.offset([-1, 0, 0]), 194 | pos.offset([0, 1, 0]), 195 | pos.offset([0, -1, 0]), 196 | pos.offset([0, 0, 1]), 197 | pos.offset([0, 0, -1]), 198 | ]; 199 | 200 | for dir in dirs.into_iter() { 201 | let neighbor_light = access.light(dir).unwrap().block(); 202 | let new_light = u16::max(queue_light - 1, neighbor_light); 203 | 204 | let id = access.block(dir).unwrap(); 205 | let neighbor_transmissible = access.registry().get(id).light_transmissible(); 206 | 207 | if new_light != neighbor_light && neighbor_transmissible { 208 | access.set_block_light(dir, new_light).unwrap(); 209 | queues.block_update.push_back((dir, new_light)); 210 | } 211 | } 212 | } 213 | } 214 | 215 | pub(crate) fn propagate_sky_light(queues: &mut LightUpdateQueues, access: &mut MutableChunkAccess) { 216 | for &(pos, _) in queues.sky_removal.iter() { 217 | access.set_sky_light(pos, 0).unwrap(); 218 | } 219 | 220 | while let Some((pos, light)) = queues.sky_removal.pop_front() { 221 | let dirs = [ 222 | pos.offset([1, 0, 0]), 223 | pos.offset([-1, 0, 0]), 224 | pos.offset([0, 1, 0]), 225 | pos.offset([0, -1, 0]), 226 | pos.offset([0, 0, 1]), 227 | pos.offset([0, 0, -1]), 228 | ]; 229 | 230 | for dir in dirs.into_iter() { 231 | let neighbor_light = access.light(dir).unwrap().sky(); 232 | 233 | if neighbor_light > 0 && neighbor_light < light { 234 | access.set_sky_light(dir, 0).unwrap(); 235 | queues.sky_removal.push_back((dir, light - 1)); 236 | } else if neighbor_light > 0 { 237 | queues.sky_update.push_back((dir, 0)); 238 | } 239 | } 240 | } 241 | 242 | while let Some((pos, queue_light)) = queues.sky_update.pop_front() { 243 | let current_light = access.light(pos).unwrap().sky(); 244 | let queue_light = u16::max(queue_light, current_light); 245 | if queue_light != current_light { 246 | access.set_sky_light(pos, queue_light).unwrap(); 247 | } 248 | 249 | if queue_light == 0 { 250 | continue; 251 | } 252 | 253 | let dirs = [ 254 | pos.offset([1, 0, 0]), 255 | pos.offset([-1, 0, 0]), 256 | pos.offset([0, 1, 0]), 257 | pos.offset([0, -1, 0]), 258 | pos.offset([0, 0, 1]), 259 | pos.offset([0, 0, -1]), 260 | ]; 261 | 262 | for dir in dirs.into_iter() { 263 | let neighbor_light = access.light(dir).unwrap().sky(); 264 | let new_light = u16::max(queue_light - 1, neighbor_light); 265 | 266 | let id = access.block(dir).unwrap(); 267 | let neighbor_transmissible = access.registry().get(id).light_transmissible(); 268 | 269 | if new_light != neighbor_light && neighbor_transmissible { 270 | access.set_sky_light(dir, new_light).unwrap(); 271 | queues.sky_update.push_back((dir, new_light)); 272 | } 273 | } 274 | } 275 | } 276 | 277 | #[derive(Clone, Debug)] 278 | pub struct SkyLightNode { 279 | // each entry in this map represents a range that starts at a height specified by the key, and 280 | // ends at the next entry's key (exclusive) 281 | // 282 | // INVARIANT: for a node N, len(N) must be odd. 283 | // INVARIANT: for a node N, solid(N[a]) must not equal solid(N[a + 1]) 284 | intervals: BTreeMap, 285 | } 286 | 287 | impl SkyLightNode { 288 | fn init(y: i32, solid: bool) -> Self { 289 | let mut intervals = BTreeMap::new(); 290 | intervals.insert(y, solid); 291 | Self { intervals } 292 | } 293 | 294 | fn lookup(&self, y: i32) -> (i32, i32, bool) { 295 | let (min, solid) = match self 296 | .intervals 297 | .range((Bound::Unbounded, Bound::Included(y))) 298 | .rev() 299 | .next() 300 | { 301 | Some((&min, &solid)) => (min, solid), 302 | // TODO: what if the bottom-most node is not solid? 303 | None => (i32::MIN, true), 304 | }; 305 | 306 | let max = match self 307 | .intervals 308 | .range((Bound::Excluded(y), Bound::Unbounded)) 309 | .next() 310 | { 311 | Some((&max, _)) => max, 312 | None => i32::MAX, 313 | }; 314 | 315 | (min, max, solid) 316 | } 317 | 318 | pub fn update(&mut self, y: i32, solid: bool) { 319 | let (min, max, interval_solid) = self.lookup(y); 320 | 321 | if solid != interval_solid { 322 | match y == min { 323 | true => drop(self.intervals.remove(&min)), 324 | false => drop(self.intervals.insert(y, solid)), 325 | } 326 | 327 | match y + 1 == max { 328 | true => drop(self.intervals.remove(&max)), 329 | false => drop(self.intervals.insert(y + 1, !solid)), 330 | } 331 | } 332 | } 333 | 334 | pub fn top(&self) -> i32 { 335 | self.intervals 336 | .keys() 337 | .rev() 338 | .next() 339 | .copied() 340 | .unwrap_or(i32::MIN) 341 | } 342 | } 343 | 344 | #[derive(Clone, Debug)] 345 | pub struct SkyLightColumns { 346 | nodes: Box<[SkyLightNode]>, 347 | } 348 | 349 | impl SkyLightColumns { 350 | pub fn initialize(heightmap: &SurfaceHeightmap) -> Self { 351 | let mut nodes = Vec::with_capacity(CHUNK_LENGTH_2); 352 | 353 | for i in 0..nodes.capacity() { 354 | nodes.push(SkyLightNode::init(heightmap.data()[i], false)); 355 | } 356 | 357 | Self { 358 | nodes: nodes.into_boxed_slice(), 359 | } 360 | } 361 | 362 | pub fn node(&self, x: usize, z: usize) -> &SkyLightNode { 363 | &self.nodes[CHUNK_LENGTH * x + z] 364 | } 365 | 366 | pub fn node_mut(&mut self, x: usize, z: usize) -> &mut SkyLightNode { 367 | &mut self.nodes[CHUNK_LENGTH * x + z] 368 | } 369 | } 370 | 371 | impl Encode for SkyLightColumns { 372 | const KIND: NodeKind = NodeKind::List; 373 | 374 | fn encode(&self, mut encoder: Encoder) -> Result<()> { 375 | // encoder.encode_rle_list(self.nodes) 376 | todo!() 377 | } 378 | } 379 | 380 | // TODO: compress! could likely both palletize and run-length encode here 381 | // impl Codec for SkyLightColumns { 382 | // fn encode(&self, writer: &mut W) -> Result<()> { 383 | // for node in self.nodes.iter() { 384 | // node.intervals.len().encode(writer)?; 385 | // for height in node.intervals.keys() { 386 | // height.encode(writer)?; 387 | // } 388 | // } 389 | // Ok(()) 390 | // } 391 | 392 | // fn decode(reader: &mut R) -> Result { 393 | // let mut nodes = Vec::with_capacity(CHUNK_LENGTH_2); 394 | // for _ in 0..nodes.capacity() { 395 | // let intervals_size = usize::decode(reader)?; 396 | // let mut intervals = BTreeMap::new(); 397 | // for i in 0..intervals_size { 398 | // intervals.insert(i32::decode(reader)?, i % 2 == 1); 399 | // } 400 | // nodes.push(SkyLightNode { intervals }); 401 | // } 402 | 403 | // Ok(SkyLightColumns { 404 | // nodes: nodes.into_boxed_slice(), 405 | // }) 406 | // } 407 | // } 408 | -------------------------------------------------------------------------------- /notcraft-common/src/world/orphan.rs: -------------------------------------------------------------------------------- 1 | use std::{ 2 | cell::UnsafeCell, 3 | sync::{ 4 | atomic::{AtomicBool, Ordering}, 5 | Arc, 6 | }, 7 | }; 8 | 9 | use arc_swap::{ArcSwap, Guard}; 10 | use parking_lot::{lock_api::RawRwLock as RawRwLockApi, RawRwLock}; 11 | 12 | struct OrphanInner { 13 | lock: RawRwLock, 14 | orphaned: AtomicBool, 15 | value: UnsafeCell, 16 | } 17 | 18 | impl OrphanInner { 19 | fn new(value: T) -> Self { 20 | Self { 21 | lock: RawRwLock::INIT, 22 | orphaned: AtomicBool::new(false), 23 | value: UnsafeCell::new(value), 24 | } 25 | } 26 | } 27 | 28 | impl Default for OrphanInner { 29 | fn default() -> Self { 30 | Self { 31 | lock: RawRwLock::INIT, 32 | orphaned: AtomicBool::new(false), 33 | value: Default::default(), 34 | } 35 | } 36 | } 37 | 38 | impl std::fmt::Debug for OrphanInner { 39 | fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 40 | f.debug_struct("OrphanInner") 41 | .field("orphaned", &self.orphaned) 42 | .field("value", &self.value) 43 | .finish_non_exhaustive() 44 | } 45 | } 46 | 47 | #[derive(Clone)] 48 | pub struct OrphanSnapshot { 49 | inner: Arc>, 50 | } 51 | 52 | unsafe impl Send for OrphanSnapshot {} 53 | unsafe impl Sync for OrphanSnapshot {} 54 | 55 | impl OrphanSnapshot { 56 | pub fn acquire(orphan: &Orphan, wait: bool) -> Option { 57 | let current_inner = orphan.current_inner.load(); 58 | match wait { 59 | true => current_inner.lock.lock_shared(), 60 | false => current_inner.lock.try_lock_shared().then(|| ())?, 61 | } 62 | Some(Self { 63 | inner: Guard::into_inner(current_inner), 64 | }) 65 | } 66 | 67 | /// Returns whether this snapshot has been fully orphaned. 68 | /// 69 | /// It is important to note that it is possible for this to return `false` 70 | /// and at the same time have [`Orphan::snapshot`] return a snapshot 71 | /// other than this one. However, if this returns `true`, then it is 72 | /// certain that this snapshot is not the most recent. 73 | pub fn is_orphaned(&self) -> bool { 74 | self.inner.orphaned.load(Ordering::Relaxed) 75 | } 76 | } 77 | 78 | impl Drop for OrphanSnapshot { 79 | fn drop(&mut self) { 80 | unsafe { self.inner.lock.unlock_shared() }; 81 | } 82 | } 83 | 84 | impl std::ops::Deref for OrphanSnapshot { 85 | type Target = T; 86 | 87 | fn deref(&self) -> &Self::Target { 88 | unsafe { &*self.inner.value.get() } 89 | } 90 | } 91 | 92 | pub struct OrphanWriter { 93 | inner: Arc>, 94 | was_cloned: bool, 95 | } 96 | 97 | impl OrphanWriter { 98 | pub fn was_cloned(this: &OrphanWriter) -> bool { 99 | this.was_cloned 100 | } 101 | } 102 | 103 | unsafe impl Send for OrphanWriter {} 104 | unsafe impl Sync for OrphanWriter {} 105 | 106 | impl OrphanWriter { 107 | pub fn acquire(orphan: &Orphan, wait_shared: bool) -> Option { 108 | let current_inner = orphan.current_inner.load(); 109 | if current_inner.lock.try_lock_exclusive() { 110 | Some(OrphanWriter { 111 | inner: Guard::into_inner(current_inner), 112 | was_cloned: false, 113 | }) 114 | } else { 115 | match wait_shared { 116 | true => current_inner.lock.lock_shared(), 117 | false => current_inner.lock.try_lock_shared().then(|| ())?, 118 | } 119 | 120 | let value = unsafe { (*current_inner.value.get()).clone() }; 121 | let inner = Arc::new(OrphanInner::new(value)); 122 | inner.lock.lock_exclusive(); 123 | 124 | orphan.current_inner.store(Arc::clone(&inner)); 125 | current_inner.orphaned.store(true, Ordering::Relaxed); 126 | 127 | Some(OrphanWriter { 128 | inner, 129 | was_cloned: true, 130 | }) 131 | } 132 | } 133 | } 134 | 135 | impl Drop for OrphanWriter { 136 | fn drop(&mut self) { 137 | unsafe { self.inner.lock.unlock_exclusive() }; 138 | } 139 | } 140 | 141 | impl std::ops::Deref for OrphanWriter { 142 | type Target = T; 143 | 144 | fn deref(&self) -> &Self::Target { 145 | unsafe { &*self.inner.value.get() } 146 | } 147 | } 148 | 149 | impl std::ops::DerefMut for OrphanWriter { 150 | fn deref_mut(&mut self) -> &mut Self::Target { 151 | unsafe { &mut *self.inner.value.get() } 152 | } 153 | } 154 | 155 | #[derive(Debug, Default)] 156 | pub struct Orphan { 157 | current_inner: ArcSwap>, 158 | } 159 | 160 | unsafe impl Send for Orphan {} 161 | unsafe impl Sync for Orphan {} 162 | 163 | impl Orphan { 164 | pub fn try_orphan_readers(&self) -> Option> { 165 | OrphanWriter::acquire(self, false) 166 | } 167 | 168 | pub fn orphan_readers(&self) -> OrphanWriter { 169 | OrphanWriter::acquire(self, true).unwrap() 170 | } 171 | 172 | pub fn try_snapshot(&self) -> Option> { 173 | OrphanSnapshot::acquire(self, false) 174 | } 175 | 176 | pub fn snapshot(&self) -> OrphanSnapshot { 177 | OrphanSnapshot::acquire(self, true).unwrap() 178 | } 179 | 180 | pub fn clone_inner(&self) -> T { 181 | T::clone(&self.snapshot()) 182 | } 183 | } 184 | 185 | impl Orphan { 186 | pub fn new(value: T) -> Self { 187 | Self { 188 | current_inner: ArcSwap::from_pointee(OrphanInner::new(value)), 189 | } 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /notcraft-common/src/world/persistence.rs: -------------------------------------------------------------------------------- 1 | use super::{chunk::Chunk, LoadEvents}; 2 | use crate::prelude::*; 3 | use std::sync::Arc; 4 | 5 | pub struct RegionPos { 6 | pub x: i32, 7 | pub z: i32, 8 | } 9 | 10 | pub struct WorldPersistence { 11 | // loaded_in_region: HashMap>, 12 | } 13 | 14 | impl WorldPersistence { 15 | pub fn new() -> Self { 16 | Self {} 17 | } 18 | 19 | pub fn save_chunk(&mut self, chunk: &Arc) -> Result<()> { 20 | todo!() 21 | } 22 | 23 | pub fn load_chunk(&mut self) -> Result { 24 | todo!() 25 | } 26 | } 27 | 28 | pub fn update_persistence(persistence: ResMut, load_events: LoadEvents) {} 29 | -------------------------------------------------------------------------------- /notcraft-common/src/world/registry.rs: -------------------------------------------------------------------------------- 1 | use crate::{ 2 | codec::{ 3 | encode::{Encode, Encoder}, 4 | NodeKind, 5 | }, 6 | prelude::*, 7 | Faces, 8 | }; 9 | use serde::Deserialize; 10 | use std::{ 11 | collections::HashMap, 12 | fs::File, 13 | path::{Path, PathBuf}, 14 | sync::Arc, 15 | }; 16 | 17 | pub const AIR_BLOCK: BlockId = BlockId(0); 18 | 19 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] 20 | pub struct TexturePoolId(usize); 21 | 22 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] 23 | pub struct TextureId(pub usize); 24 | 25 | #[derive(Clone, Debug, PartialEq, Default, Deserialize)] 26 | #[serde(rename_all = "kebab-case")] 27 | #[serde(default)] 28 | pub struct BlockTextureReference { 29 | /// fallback texture to use when a face is not specified. if neither a face 30 | /// nor a default is provided, the "unknown" texture is used. 31 | default: Option, 32 | 33 | /// references into a pool of textures that represent texture variants that 34 | /// are selected randomly. 35 | #[serde(flatten)] 36 | faces: Faces>, 37 | } 38 | 39 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Deserialize)] 40 | #[serde(rename_all = "kebab-case")] 41 | pub enum CollisionType { 42 | None, 43 | Solid, 44 | Liquid, 45 | } 46 | 47 | impl CollisionType { 48 | /// Returns `true` if the collision type is [`Solid`]. 49 | /// 50 | /// [`Solid`]: CollisionType::Solid 51 | pub fn is_solid(&self) -> bool { 52 | matches!(self, Self::Solid) 53 | } 54 | 55 | /// Returns `true` if the collision type is [`Liquid`]. 56 | /// 57 | /// [`Liquid`]: CollisionType::Liquid 58 | pub fn is_liquid(&self) -> bool { 59 | matches!(self, Self::Liquid) 60 | } 61 | } 62 | 63 | #[derive(Clone, Debug, PartialEq, Deserialize)] 64 | #[serde(rename_all = "kebab-case")] 65 | pub struct BlockProperties { 66 | collision_type: CollisionType, 67 | #[serde(default)] 68 | liquid: bool, 69 | #[serde(default)] 70 | wind_sway: bool, 71 | #[serde(default)] 72 | block_light: u16, 73 | #[serde(default)] 74 | light_transmissible: bool, 75 | #[serde(default)] 76 | break_when_unrooted: bool, 77 | } 78 | 79 | #[derive(Copy, Clone, Debug, PartialEq, Deserialize)] 80 | #[serde(rename_all = "kebab-case")] 81 | pub enum BlockMeshType { 82 | None, 83 | FullCube, 84 | Cross, 85 | } 86 | 87 | impl Default for BlockMeshType { 88 | fn default() -> Self { 89 | Self::FullCube 90 | } 91 | } 92 | 93 | #[derive(Clone, Debug, PartialEq, Deserialize)] 94 | #[serde(rename_all = "kebab-case")] 95 | pub struct BlockDescription { 96 | name: String, 97 | properties: BlockProperties, 98 | #[serde(default)] 99 | mesh_type: BlockMeshType, 100 | 101 | /// a list of texture variants to use for this block. 102 | /// 103 | /// note that there is a variant list here, as well as inside 104 | /// [`BlockTextureReference`]. the difference is that here, variants change 105 | /// the textures for the entire block, which inside the texture reference, 106 | /// variants change the textures for just that block face. 107 | #[serde(default)] 108 | texture_variants: Option>, 109 | } 110 | 111 | #[derive(Clone, Debug, PartialEq)] 112 | pub struct BlockRegistryEntry { 113 | name: String, 114 | properties: BlockProperties, 115 | mesh_type: BlockMeshType, 116 | textures: Option>>, 117 | } 118 | 119 | #[derive(Copy, Clone, Debug, Eq, PartialEq, Hash, Default)] 120 | pub struct BlockId(pub(crate) usize); 121 | 122 | impl Encode for BlockId { 123 | const KIND: NodeKind = NodeKind::UnsignedVarInt; 124 | 125 | fn encode(&self, encoder: Encoder) -> Result<()> { 126 | encoder.encode(&self.0) 127 | } 128 | } 129 | 130 | fn add_texture_to_pool(reg: &mut BlockRegistry, pool: TexturePoolId, path: &Path) -> TextureId { 131 | let pool = &mut reg.texture_pools[pool.0]; 132 | 133 | let id = TextureId(reg.texture_paths.len()); 134 | reg.texture_indices.insert(path.into(), id); 135 | reg.texture_paths.push(path.into()); 136 | 137 | pool.push(id); 138 | id 139 | } 140 | 141 | fn register_texture_pool(reg: &mut BlockRegistry, name: &str) -> TexturePoolId { 142 | if let Some(&idx) = reg.texture_pool_indices.get(name) { 143 | return idx; 144 | } 145 | 146 | let id = TexturePoolId(reg.texture_pools.len()); 147 | reg.texture_pool_indices.insert(name.into(), id); 148 | reg.texture_pools.push(vec![]); 149 | 150 | id 151 | } 152 | 153 | fn make_entry(reg: &mut BlockRegistry, desc: BlockDescription) -> Result { 154 | let textures = match desc.texture_variants { 155 | Some(variants) => { 156 | let mut res = Vec::with_capacity(variants.len()); 157 | for variant in variants { 158 | let default = variant 159 | .default 160 | .map(|path| reg.texture_pool_indices[&path]) 161 | .unwrap_or_else(|| reg.texture_pool_indices["unknown"]); 162 | res.push(variant.faces.map(|path| { 163 | path.map(|path| reg.texture_pool_indices[&path]) 164 | .unwrap_or(default) 165 | })); 166 | } 167 | Some(res) 168 | } 169 | None => None, 170 | }; 171 | 172 | Ok(BlockRegistryEntry { 173 | name: desc.name, 174 | properties: desc.properties, 175 | mesh_type: desc.mesh_type, 176 | textures, 177 | }) 178 | } 179 | 180 | #[derive(Clone, Debug, PartialEq, Default)] 181 | pub struct BlockRegistry { 182 | name_map: HashMap, 183 | entries: Vec, 184 | 185 | // the order here is important: the indices will becomes layers in a texture array that holds 186 | // the actual texture data. 187 | texture_paths: Vec, 188 | // texture *paths* to texture ID 189 | texture_indices: HashMap, 190 | 191 | // texture pool *names* to texture ID 192 | texture_pools: Vec>, 193 | texture_pool_indices: HashMap, 194 | } 195 | 196 | #[derive(Clone, Debug, PartialEq, Deserialize)] 197 | #[serde(rename_all = "kebab-case")] 198 | struct RegistryManifest { 199 | textures: HashMap>, 200 | blocks: Vec, 201 | } 202 | 203 | pub fn load_registry>(path: P) -> Result> { 204 | let manifest: RegistryManifest = serde_json::from_reader(File::open(path)?)?; 205 | let mut registry = BlockRegistry::default(); 206 | 207 | let unknown_pool = register_texture_pool(&mut registry, "unknown"); 208 | add_texture_to_pool(&mut registry, unknown_pool, Path::new("unknown.png")); 209 | 210 | for (pool_name, paths) in manifest.textures { 211 | let pool = register_texture_pool(&mut registry, &pool_name); 212 | for path in paths { 213 | add_texture_to_pool(&mut registry, pool, Path::new(&path)); 214 | } 215 | } 216 | 217 | for block in manifest.blocks { 218 | let id = registry.entries.len(); 219 | registry.name_map.insert(block.name.clone(), BlockId(id)); 220 | let entry = make_entry(&mut registry, block)?; 221 | registry.entries.push(entry); 222 | } 223 | 224 | Ok(Arc::new(registry)) 225 | } 226 | 227 | impl BlockRegistry { 228 | pub fn get(&self, id: BlockId) -> RegistryRef { 229 | RegistryRef { registry: self, id } 230 | } 231 | 232 | pub fn lookup(&self, name: &str) -> BlockId { 233 | self.name_map[name] 234 | } 235 | 236 | pub fn name(&self, id: BlockId) -> &str { 237 | &self.entries[id.0].name 238 | } 239 | 240 | pub fn texture_paths<'a>(&'a self) -> impl Iterator { 241 | self.texture_paths.iter().map(|s| &**s) 242 | } 243 | 244 | #[inline(always)] 245 | pub fn pool_textures(&self, id: TexturePoolId) -> &[TextureId] { 246 | &self.texture_pools[id.0] 247 | } 248 | } 249 | 250 | pub struct RegistryRef<'reg> { 251 | registry: &'reg BlockRegistry, 252 | id: BlockId, 253 | } 254 | 255 | impl<'reg> RegistryRef<'reg> { 256 | pub fn registry(&self) -> &'reg BlockRegistry { 257 | self.registry 258 | } 259 | 260 | #[inline(always)] 261 | pub fn name(&self) -> &str { 262 | &self.registry.entries[self.id.0].name 263 | } 264 | 265 | #[inline(always)] 266 | pub fn collision_type(&self) -> CollisionType { 267 | self.registry.entries[self.id.0].properties.collision_type 268 | } 269 | 270 | #[inline(always)] 271 | pub fn liquid(&self) -> bool { 272 | self.registry.entries[self.id.0].properties.liquid 273 | } 274 | 275 | #[inline(always)] 276 | pub fn wind_sway(&self) -> bool { 277 | self.registry.entries[self.id.0].properties.wind_sway 278 | } 279 | 280 | #[inline(always)] 281 | pub fn block_light(&self) -> u16 { 282 | self.registry.entries[self.id.0].properties.block_light 283 | } 284 | 285 | #[inline(always)] 286 | pub fn light_transmissible(&self) -> bool { 287 | self.registry.entries[self.id.0] 288 | .properties 289 | .light_transmissible 290 | } 291 | 292 | #[inline(always)] 293 | pub fn break_when_unrooted(&self) -> bool { 294 | self.registry.entries[self.id.0] 295 | .properties 296 | .break_when_unrooted 297 | } 298 | 299 | #[inline(always)] 300 | pub fn mesh_type(&self) -> BlockMeshType { 301 | self.registry.entries[self.id.0].mesh_type 302 | } 303 | 304 | #[inline(always)] 305 | pub fn block_textures(&self) -> Option<&'reg Vec>> { 306 | self.registry.entries[self.id.0].textures.as_ref() 307 | } 308 | } 309 | -------------------------------------------------------------------------------- /resources/audio/blocks/568422__sheyvan__friction-small-stone-1-1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/568422__sheyvan__friction-small-stone-1-1.wav -------------------------------------------------------------------------------- /resources/audio/blocks/568423__sheyvan__friction-small-stone-1-2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/568423__sheyvan__friction-small-stone-1-2.wav -------------------------------------------------------------------------------- /resources/audio/blocks/568424__sheyvan__impact-small-stone-1-1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/568424__sheyvan__impact-small-stone-1-1.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569497__sheyvan__stone-impact-rubble-debris-1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569497__sheyvan__stone-impact-rubble-debris-1.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569498__sheyvan__stone-impact-rubble-debris-2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569498__sheyvan__stone-impact-rubble-debris-2.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569499__sheyvan__stone-impact-rubble-debris-3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569499__sheyvan__stone-impact-rubble-debris-3.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569500__sheyvan__stone-impact-rubble-debris-4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569500__sheyvan__stone-impact-rubble-debris-4.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569501__sheyvan__stone-impact-rubble-debris-5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569501__sheyvan__stone-impact-rubble-debris-5.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569502__sheyvan__stone-impact-rubble-debris-6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569502__sheyvan__stone-impact-rubble-debris-6.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569503__sheyvan__stone-impact-rubble-debris-7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569503__sheyvan__stone-impact-rubble-debris-7.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569504__sheyvan__stone-impact-rubble-debris-8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569504__sheyvan__stone-impact-rubble-debris-8.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569505__sheyvan__stone-impact-rubble-debris-9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569505__sheyvan__stone-impact-rubble-debris-9.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569506__sheyvan__stone-impact-rubble-debris-10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569506__sheyvan__stone-impact-rubble-debris-10.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569507__sheyvan__stone-impact-rubble-debris-11.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569507__sheyvan__stone-impact-rubble-debris-11.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569508__sheyvan__stone-impact-rubble-debris-12.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569508__sheyvan__stone-impact-rubble-debris-12.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569509__sheyvan__stone-impact-rubble-debris-13.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569509__sheyvan__stone-impact-rubble-debris-13.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569510__sheyvan__stone-impact-rubble-debris-14.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569510__sheyvan__stone-impact-rubble-debris-14.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569734__sheyvan__gravel-stone-dirt-debris-falling-small-1-1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569734__sheyvan__gravel-stone-dirt-debris-falling-small-1-1.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569735__sheyvan__gravel-stone-dirt-debris-falling-small-1-2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569735__sheyvan__gravel-stone-dirt-debris-falling-small-1-2.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569736__sheyvan__gravel-stone-dirt-debris-falling-small-1-3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569736__sheyvan__gravel-stone-dirt-debris-falling-small-1-3.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569737__sheyvan__gravel-stone-dirt-debris-falling-small-1-4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569737__sheyvan__gravel-stone-dirt-debris-falling-small-1-4.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569738__sheyvan__gravel-stone-dirt-debris-falling-small-1-5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569738__sheyvan__gravel-stone-dirt-debris-falling-small-1-5.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569739__sheyvan__gravel-stone-dirt-debris-falling-small-1-6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569739__sheyvan__gravel-stone-dirt-debris-falling-small-1-6.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569740__sheyvan__gravel-stone-dirt-debris-falling-small-1-7.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569740__sheyvan__gravel-stone-dirt-debris-falling-small-1-7.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569741__sheyvan__gravel-stone-dirt-debris-falling-small-1-8.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569741__sheyvan__gravel-stone-dirt-debris-falling-small-1-8.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569742__sheyvan__gravel-stone-dirt-debris-falling-small-1-9.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569742__sheyvan__gravel-stone-dirt-debris-falling-small-1-9.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569743__sheyvan__gravel-stone-dirt-debris-falling-small-1-10.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569743__sheyvan__gravel-stone-dirt-debris-falling-small-1-10.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569744__sheyvan__gravel-stone-dirt-debris-falling-small-1-11.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569744__sheyvan__gravel-stone-dirt-debris-falling-small-1-11.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569745__sheyvan__gravel-stone-dirt-debris-falling-small-1-12.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569745__sheyvan__gravel-stone-dirt-debris-falling-small-1-12.wav -------------------------------------------------------------------------------- /resources/audio/blocks/569746__sheyvan__gravel-stone-dirt-debris-falling-small-1-13.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/569746__sheyvan__gravel-stone-dirt-debris-falling-small-1-13.wav -------------------------------------------------------------------------------- /resources/audio/blocks/bassy-dirt-hit-1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/bassy-dirt-hit-1.wav -------------------------------------------------------------------------------- /resources/audio/blocks/bassy-dirt-hit-2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/bassy-dirt-hit-2.wav -------------------------------------------------------------------------------- /resources/audio/blocks/dirt-hit-1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/dirt-hit-1.wav -------------------------------------------------------------------------------- /resources/audio/blocks/dirt-hit-2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/dirt-hit-2.wav -------------------------------------------------------------------------------- /resources/audio/blocks/dirt-hit-3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/dirt-hit-3.wav -------------------------------------------------------------------------------- /resources/audio/blocks/tall-grass-hit-1.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/tall-grass-hit-1.wav -------------------------------------------------------------------------------- /resources/audio/blocks/tall-grass-hit-2.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/tall-grass-hit-2.wav -------------------------------------------------------------------------------- /resources/audio/blocks/tall-grass-hit-3.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/tall-grass-hit-3.wav -------------------------------------------------------------------------------- /resources/audio/blocks/tall-grass-hit-4.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/tall-grass-hit-4.wav -------------------------------------------------------------------------------- /resources/audio/blocks/tall-grass-hit-5.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/tall-grass-hit-5.wav -------------------------------------------------------------------------------- /resources/audio/blocks/tall-grass-hit-6.wav: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/blocks/tall-grass-hit-6.wav -------------------------------------------------------------------------------- /resources/audio/credits.md: -------------------------------------------------------------------------------- 1 | # Credits 2 | 3 | ## Audio from [Sheyvan] 4 | * From [this](https://freesound.org/people/Sheyvan/packs/31936/) audio pack: 5 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-13.wav](https://freesound.org/s/569746/) 6 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-12.wav](https://freesound.org/s/569745/) 7 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-11.wav](https://freesound.org/s/569744/) 8 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-10.wav](https://freesound.org/s/569743/) 9 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-9.wav](https://freesound.org/s/569742/) 10 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-8.wav](https://freesound.org/s/569741/) 11 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-7.wav](https://freesound.org/s/569740/) 12 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-6.wav](https://freesound.org/s/569739/) 13 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-5.wav](https://freesound.org/s/569738/) 14 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-4.wav](https://freesound.org/s/569737/) 15 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-3.wav](https://freesound.org/s/569736/) 16 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-2.wav](https://freesound.org/s/569735/) 17 | * *[CC-0]* [gravel-stone-dirt-debris-falling-small-1-1.wav](https://freesound.org/s/569734/) 18 | * *[CC-0]* [stone-impact-rubble-debris-14.wav](https://freesound.org/s/569510/) 19 | * *[CC-0]* [stone-impact-rubble-debris-13.wav](https://freesound.org/s/569509/) 20 | * *[CC-0]* [stone-impact-rubble-debris-12.wav](https://freesound.org/s/569508/) 21 | * *[CC-0]* [stone-impact-rubble-debris-11.wav](https://freesound.org/s/569507/) 22 | * *[CC-0]* [stone-impact-rubble-debris-10.wav](https://freesound.org/s/569506/) 23 | * *[CC-0]* [stone-impact-rubble-debris-9.wav](https://freesound.org/s/569505/) 24 | * *[CC-0]* [stone-impact-rubble-debris-8.wav](https://freesound.org/s/569504/) 25 | * *[CC-0]* [stone-impact-rubble-debris-7.wav](https://freesound.org/s/569503/) 26 | * *[CC-0]* [stone-impact-rubble-debris-6.wav](https://freesound.org/s/569502/) 27 | * *[CC-0]* [stone-impact-rubble-debris-5.wav](https://freesound.org/s/569501/) 28 | * *[CC-0]* [stone-impact-rubble-debris-4.wav](https://freesound.org/s/569500/) 29 | * *[CC-0]* [stone-impact-rubble-debris-3.wav](https://freesound.org/s/569499/) 30 | * *[CC-0]* [stone-impact-rubble-debris-2.wav](https://freesound.org/s/569498/) 31 | * *[CC-0]* [stone-impact-rubble-debris-1.wav](https://freesound.org/s/569497/) 32 | * *[CC-0]* [impact-small-stone-1-1.wav](https://freesound.org/s/568424/) 33 | * *[CC-0]* [friction-small-stone-1-2.wav](https://freesound.org/s/568423/) 34 | * *[CC-0]* [friction-small-stone-1-1.wav](https://freesound.org/s/568422/) 35 | 36 | ## Audio from [worthahep88] 37 | *[CC-0]* [tall-grass-hit-2.wav](https://freesound.org/s/319215/) (chopped) 38 | 39 | [Sheyvan]: https://freesound.org/people/Sheyvan/ 40 | [worthahep88]: https://freesound.org/people/worthahep88/ 41 | [CC-0]: http://creativecommons.org/publicdomain/zero/1.0/ 42 | -------------------------------------------------------------------------------- /resources/audio/manifest.ron: -------------------------------------------------------------------------------- 1 | #![enable(implicit_some)] 2 | 3 | AudioManifest({ 4 | "blocksound": Pool(min_pitch: 0.7, max_pitch: 1.2), 5 | 6 | "stone-hit": Pool(inherit: "blocksound", patterns: ["blocks/*stone-impact-rubble-debris*"]), 7 | "grass-hit": Pool(inherit: "blocksound", patterns: ["blocks/tall-grass-hit-*"]), 8 | "dirt-hit-bassy": Pool(inherit: "blocksound", patterns: ["blocks/bassy-dirt-hit-*"]), 9 | "dirt-hit": Pool(inherit: "blocksound", patterns: ["blocks/dirt-hit-*"]), 10 | 11 | "music": Pool(patterns: ["music/*", "music/**"]), 12 | 13 | "blocks/break/stone": Ref("stone-hit"), 14 | "blocks/break/dirt": Layered( 15 | default: Ref("dirt-hit"), 16 | layers: [ 17 | (1.0, Ref("dirt-hit")), 18 | (0.5, Ref("dirt-hit-bassy")), 19 | ], 20 | ), 21 | "blocks/break/grass": Layered( 22 | default: Ref("grass-hit"), 23 | layers: [ 24 | (1.0, Pool(inherit: "dirt-hit-bassy", min_amplitude: 0.0, max_amplitude: 0.0)), 25 | (1.0, Ref("grass-hit")), 26 | ], 27 | ), 28 | }) 29 | -------------------------------------------------------------------------------- /resources/audio/music/ambient1.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/audio/music/ambient1.mp3 -------------------------------------------------------------------------------- /resources/blocks.json: -------------------------------------------------------------------------------- 1 | { 2 | "textures": { 3 | "stone": [ 4 | "stone.png" 5 | ], 6 | "dirt": [ 7 | "dirt.png" 8 | ], 9 | "grass-top": [ 10 | "grass.png", 11 | "grass_variant_1.png", 12 | "grass_variant_2.png", 13 | "grass_variant_3.png", 14 | "grass_variant_4.png", 15 | "grass_variant_5.png", 16 | "grass_variant_6.png", 17 | "grass_variant_7.png" 18 | ], 19 | "grass-side": [ 20 | "grass_side.png" 21 | ], 22 | "sand": [ 23 | "sand.png" 24 | ], 25 | "water": [ 26 | "water.png" 27 | ], 28 | "detail-grass": [ 29 | "detail_short_grass.png", 30 | "detail_medium_grass.png" 31 | ] 32 | }, 33 | "blocks": [ 34 | { 35 | "name": "air", 36 | "mesh-type": "none", 37 | "properties": { 38 | "collision-type": "none", 39 | "light-transmissible": true, 40 | "liquid": false 41 | } 42 | }, 43 | { 44 | "name": "stone", 45 | "mesh-type": "full-cube", 46 | "properties": { 47 | "collision-type": "solid", 48 | "light-transmissible": false, 49 | "liquid": false 50 | }, 51 | "texture-variants": [ 52 | { 53 | "default": "stone" 54 | } 55 | ] 56 | }, 57 | { 58 | "name": "dirt", 59 | "mesh-type": "full-cube", 60 | "properties": { 61 | "collision-type": "solid", 62 | "light-transmissible": false, 63 | "liquid": false 64 | }, 65 | "texture-variants": [ 66 | { 67 | "default": "dirt" 68 | } 69 | ] 70 | }, 71 | { 72 | "name": "grass", 73 | "mesh-type": "full-cube", 74 | "properties": { 75 | "collision-type": "solid", 76 | "light-transmissible": false, 77 | "liquid": false 78 | }, 79 | "texture-variants": [ 80 | { 81 | "default": "grass-side", 82 | "top": "grass-top", 83 | "bottom": "dirt" 84 | } 85 | ] 86 | }, 87 | { 88 | "name": "sand", 89 | "mesh-type": "full-cube", 90 | "properties": { 91 | "collision-type": "solid", 92 | "light-transmissible": false, 93 | "liquid": false 94 | }, 95 | "texture-variants": [ 96 | { 97 | "default": "sand" 98 | } 99 | ] 100 | }, 101 | { 102 | "name": "water", 103 | "mesh-type": "full-cube", 104 | "properties": { 105 | "collision-type": "liquid", 106 | "light-transmissible": true, 107 | "liquid": true 108 | }, 109 | "texture-variants": [ 110 | { 111 | "default": "water" 112 | } 113 | ] 114 | }, 115 | { 116 | "name": "detail_grass", 117 | "mesh-type": "cross", 118 | "properties": { 119 | "collision-type": "none", 120 | "light-transmissible": true, 121 | "break-when-unrooted": true, 122 | "wind-sway": true, 123 | "liquid": false 124 | }, 125 | "texture-variants": [ 126 | { 127 | "default": "detail-grass" 128 | } 129 | ] 130 | }, 131 | { 132 | "name": "debug_glow_block", 133 | "mesh-type": "cross", 134 | "properties": { 135 | "collision-type": "solid", 136 | "liquid": false, 137 | "light-transmissible": true, 138 | "block-light": 15 139 | }, 140 | "texture-variants": [ 141 | { 142 | "default": "water" 143 | } 144 | ] 145 | } 146 | ] 147 | } -------------------------------------------------------------------------------- /resources/shaders/adjustables.glsl: -------------------------------------------------------------------------------- 1 | #define RGB(r, g, b) (vec3(r.0 / 255.0, g.0 / 255.0, b.0 / 255.0)) 2 | 3 | #define SKY_COLOR_BASE RGB(55, 127, 252) 4 | #define SKY_COLOR_BRIGHT RGB(148, 197, 252) 5 | #define SKY_COLOR_NIGHT_BASE RGB(0, 0, 0) 6 | #define SKY_COLOR_NIGHT_BRIGHT RGB(2, 2, 2) 7 | 8 | #define FOG_COLOR mix(SKY_COLOR_BASE, SKY_COLOR_BRIGHT, 0.5) 9 | #define FOG_COLOR_NIGHT mix(SKY_COLOR_NIGHT_BASE, SKY_COLOR_NIGHT_BRIGHT, 0.5) 10 | 11 | #define DAY_NIGHT_LENGTH 60.0 12 | #define _DAY_NIGHT_M1_1(time) sin(3.14159 * time / DAY_NIGHT_LENGTH) 13 | #define _DAY_NIGHT_0_1(time) 0.5 * _DAY_NIGHT_M1_1(time) + 0.5 14 | // #define DAY_NIGHT_FACTOR(time) smoothstep(-0.4, 0.4, _DAY_NIGHT_M1_1(time)) 15 | #define DAY_NIGHT_FACTOR(time) 1.0 16 | #define DAY_NIGHT(day, night) mix(night, day, DAY_NIGHT_FACTOR(elapsedTime())) 17 | 18 | // #define CLOUD_PLANE_DISTANCE 10000.0 19 | #define CLOUD_PLANE_DISTANCE 1000.0 20 | #define CLOUD_PLANE_DISTANCE_CUTOFF 10000.0 21 | 22 | // IDEA: use voroni noise for clouds, may end up looking much smoother and nicer that way, and less cotton-ball-like 23 | // IDEA: noise map to control cloud coverage 24 | #define CLOUD_NOISE_PARAMS NoiseParameters( \ 25 | /* octaveCount */ 5, \ 26 | /* baseFrequency */ 0.001, \ 27 | /* frequencyScale */ 3.5, \ 28 | /* baseAmplitude */ 1.0, \ 29 | /* amplitudeScale */ 0.3, \ 30 | /* scrollSpeed */ 4.0, \ 31 | /* bubbleSpeed */ 1.0 \ 32 | ); 33 | 34 | #define CLOUD_NOISE_COVERAGE_SCALE 0.0001 35 | #define CLOUD_NOISE_COVERAGE_SCROLL_SPEED 20.0 36 | #define CLOUD_NOISE_COVERAGE_SCROLL_DIRECTION vec2(0.8, 0.2) 37 | -------------------------------------------------------------------------------- /resources/shaders/crosshair.glsl: -------------------------------------------------------------------------------- 1 | #pragma shaderstage vertex 2 | #version 330 core 3 | 4 | uniform float screen_width; 5 | uniform float screen_height; 6 | 7 | in vec2 uv; 8 | out vec2 v_texcoord; 9 | 10 | void main() { 11 | vec2 pos = uv; 12 | pos.x /= (screen_width / screen_height); 13 | pos /= screen_height / 16.0; 14 | 15 | v_texcoord = 0.5 * uv + 0.5; 16 | gl_Position = vec4(pos, 0.0, 1.0); 17 | } 18 | 19 | #pragma shaderstage fragment 20 | #version 330 core 21 | 22 | uniform sampler2D crosshair_texture; 23 | 24 | in vec2 v_texcoord; 25 | out vec4 o_color; 26 | 27 | void main() { 28 | o_color = texture2D(crosshair_texture, v_texcoord); 29 | } 30 | -------------------------------------------------------------------------------- /resources/shaders/debug.glsl: -------------------------------------------------------------------------------- 1 | #pragma shaderstage vertex 2 | #version 330 core 3 | 4 | #pragma include "./util.glsl" 5 | 6 | uniform mat4 view; 7 | uniform mat4 projection; 8 | 9 | in vec3 pos; 10 | in uint color_rg; 11 | in uint color_ba; 12 | // line type + one bit for marking either the start (false) or end (true) of the line. 13 | // in uint kind_end; 14 | 15 | out vec4 v_color; 16 | out float v_end; 17 | flat out int v_kind; 18 | 19 | void main() { 20 | gl_Position = projection * view * vec4(pos, 1.0); 21 | 22 | vec4 color = vec4(0.0); 23 | color.r = float(BITS(color_rg, 16, 16)) / 65536.0; 24 | color.g = float(BITS(color_rg, 0, 16)) / 65536.0; 25 | color.b = float(BITS(color_ba, 16, 16)) / 65536.0; 26 | color.a = float(BITS(color_ba, 0, 16)) / 65536.0; 27 | 28 | v_color = color; 29 | // v_kind = int(kind_end) >> 1; 30 | // v_end = float(int(kind_end) & 1); 31 | v_kind = 0; 32 | v_end = 0.0; 33 | } 34 | 35 | #pragma shaderstage fragment 36 | #version 330 core 37 | 38 | in vec4 v_color; 39 | in float v_end; 40 | flat in int v_kind; 41 | 42 | out vec4 o_color; 43 | 44 | #define DASHES_PER_UNIT (20.0) 45 | #define DOTS_PER_UNIT (50.0) 46 | 47 | #define KIND_SOLID_LINE 0 48 | #define KIND_DASHED_LINE 1 49 | #define KIND_DOTTED_LINE 2 50 | 51 | float dotted_factor(float end_percent) { 52 | float t = end_percent * DOTS_PER_UNIT; 53 | return floor(2.0 * t) - 2.0 * floor(t); 54 | } 55 | 56 | float dashed_factor(float end_percent) { 57 | float t = end_percent * DASHES_PER_UNIT; 58 | return floor(2.0 * t) - 2.0 * floor(t); 59 | } 60 | 61 | void main() { 62 | vec4 color = v_color.rgba; 63 | switch (v_kind) { 64 | case KIND_SOLID_LINE: break; 65 | case KIND_DASHED_LINE: color.a *= dashed_factor(v_end); break; 66 | case KIND_DOTTED_LINE: color.a *= dotted_factor(v_end); break; 67 | } 68 | o_color = color; 69 | } 70 | -------------------------------------------------------------------------------- /resources/shaders/fullscreen_quad.vert: -------------------------------------------------------------------------------- 1 | #pragma shaderstage vertex 2 | #version 330 core 3 | 4 | in vec2 uv; 5 | 6 | out vec2 v_uv; 7 | out vec2 v_texcoord; 8 | 9 | void main() { 10 | v_texcoord = 0.5 * uv + 0.5; 11 | v_uv = uv; 12 | gl_Position = vec4(uv, 0.0, 1.0); 13 | } 14 | -------------------------------------------------------------------------------- /resources/shaders/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "paths": { 3 | "terrain": "terrain/main.glsl", 4 | "post": "post.glsl", 5 | "sky": "sky.glsl", 6 | "debug": "debug.glsl", 7 | "crosshair": "crosshair.glsl" 8 | } 9 | } -------------------------------------------------------------------------------- /resources/shaders/noise.glsl: -------------------------------------------------------------------------------- 1 | // noise functions taken from `https://github.com/ashima/webgl-noise`. 2 | // 3 | // Description : Array and textureless GLSL 2D simplex noise function. 4 | // Author : Ian McEwan, Ashima Arts. 5 | // Maintainer : stegu 6 | // Lastmod : 20110822 (ijm) 7 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 8 | // Distributed under the MIT License. See LICENSE file. 9 | // https://github.com/ashima/webgl-noise 10 | // https://github.com/stegu/webgl-noise 11 | // 12 | 13 | vec2 mod289(vec2 x) { 14 | return x - floor(x * (1.0 / 289.0)) * 289.0; 15 | } 16 | 17 | vec3 mod289(vec3 x) { 18 | return x - floor(x * (1.0 / 289.0)) * 289.0; 19 | } 20 | 21 | vec4 mod289(vec4 x) { 22 | return x - floor(x * (1.0 / 289.0)) * 289.0; 23 | } 24 | 25 | vec3 permute(vec3 x) { 26 | return mod289(((x*34.0)+10.0)*x); 27 | } 28 | 29 | float simplexNoise(vec2 v) { 30 | const vec4 C = vec4(0.211324865405187, // (3.0-sqrt(3.0))/6.0 31 | 0.366025403784439, // 0.5*(sqrt(3.0)-1.0) 32 | -0.577350269189626, // -1.0 + 2.0 * C.x 33 | 0.024390243902439); // 1.0 / 41.0 34 | // First corner 35 | vec2 i = floor(v + dot(v, C.yy) ); 36 | vec2 x0 = v - i + dot(i, C.xx); 37 | 38 | // Other corners 39 | vec2 i1; 40 | //i1.x = step( x0.y, x0.x ); // x0.x > x0.y ? 1.0 : 0.0 41 | //i1.y = 1.0 - i1.x; 42 | i1 = (x0.x > x0.y) ? vec2(1.0, 0.0) : vec2(0.0, 1.0); 43 | // x0 = x0 - 0.0 + 0.0 * C.xx ; 44 | // x1 = x0 - i1 + 1.0 * C.xx ; 45 | // x2 = x0 - 1.0 + 2.0 * C.xx ; 46 | vec4 x12 = x0.xyxy + C.xxzz; 47 | x12.xy -= i1; 48 | 49 | // Permutations 50 | i = mod289(i); // Avoid truncation effects in permutation 51 | vec3 p = permute( permute( i.y + vec3(0.0, i1.y, 1.0 )) 52 | + i.x + vec3(0.0, i1.x, 1.0 )); 53 | 54 | vec3 m = max(0.5 - vec3(dot(x0,x0), dot(x12.xy,x12.xy), dot(x12.zw,x12.zw)), 0.0); 55 | m = m*m ; 56 | m = m*m ; 57 | 58 | // Gradients: 41 points uniformly over a line, mapped onto a diamond. 59 | // The ring size 17*17 = 289 is close to a multiple of 41 (41*7 = 287) 60 | 61 | vec3 x = 2.0 * fract(p * C.www) - 1.0; 62 | vec3 h = abs(x) - 0.5; 63 | vec3 ox = floor(x + 0.5); 64 | vec3 a0 = x - ox; 65 | 66 | // Normalise gradients implicitly by scaling m 67 | // Approximation of: m *= inversesqrt( a0*a0 + h*h ); 68 | m *= 1.79284291400159 - 0.85373472095314 * ( a0*a0 + h*h ); 69 | 70 | // Compute final noise value at P 71 | vec3 g; 72 | g.x = a0.x * x0.x + h.x * x0.y; 73 | g.yz = a0.yz * x12.xz + h.yz * x12.yw; 74 | return 130.0 * dot(m, g); 75 | } 76 | 77 | // 78 | // Description : Array and textureless GLSL 2D/3D/4D simplex 79 | // noise functions. 80 | // Author : Ian McEwan, Ashima Arts. 81 | // Maintainer : stegu 82 | // Lastmod : 20201014 (stegu) 83 | // License : Copyright (C) 2011 Ashima Arts. All rights reserved. 84 | // Distributed under the MIT License. See LICENSE file. 85 | // https://github.com/ashima/webgl-noise 86 | // https://github.com/stegu/webgl-noise 87 | // 88 | 89 | vec4 permute(vec4 x) { 90 | return mod289(((x*34.0)+10.0)*x); 91 | } 92 | 93 | vec4 taylorInvSqrt(vec4 r) { 94 | return 1.79284291400159 - 0.85373472095314 * r; 95 | } 96 | 97 | float simplexNoise(vec3 v) { 98 | const vec2 C = vec2(1.0/6.0, 1.0/3.0) ; 99 | const vec4 D = vec4(0.0, 0.5, 1.0, 2.0); 100 | 101 | // First corner 102 | vec3 i = floor(v + dot(v, C.yyy) ); 103 | vec3 x0 = v - i + dot(i, C.xxx) ; 104 | 105 | // Other corners 106 | vec3 g = step(x0.yzx, x0.xyz); 107 | vec3 l = 1.0 - g; 108 | vec3 i1 = min( g.xyz, l.zxy ); 109 | vec3 i2 = max( g.xyz, l.zxy ); 110 | 111 | // x0 = x0 - 0.0 + 0.0 * C.xxx; 112 | // x1 = x0 - i1 + 1.0 * C.xxx; 113 | // x2 = x0 - i2 + 2.0 * C.xxx; 114 | // x3 = x0 - 1.0 + 3.0 * C.xxx; 115 | vec3 x1 = x0 - i1 + C.xxx; 116 | vec3 x2 = x0 - i2 + C.yyy; // 2.0*C.x = 1/3 = C.y 117 | vec3 x3 = x0 - D.yyy; // -1.0+3.0*C.x = -0.5 = -D.y 118 | 119 | // Permutations 120 | i = mod289(i); 121 | vec4 p = permute( permute( permute( 122 | i.z + vec4(0.0, i1.z, i2.z, 1.0 )) 123 | + i.y + vec4(0.0, i1.y, i2.y, 1.0 )) 124 | + i.x + vec4(0.0, i1.x, i2.x, 1.0 )); 125 | 126 | // Gradients: 7x7 points over a square, mapped onto an octahedron. 127 | // The ring size 17*17 = 289 is close to a multiple of 49 (49*6 = 294) 128 | float n_ = 0.142857142857; // 1.0/7.0 129 | vec3 ns = n_ * D.wyz - D.xzx; 130 | 131 | vec4 j = p - 49.0 * floor(p * ns.z * ns.z); // mod(p,7*7) 132 | 133 | vec4 x_ = floor(j * ns.z); 134 | vec4 y_ = floor(j - 7.0 * x_ ); // mod(j,N) 135 | 136 | vec4 x = x_ *ns.x + ns.yyyy; 137 | vec4 y = y_ *ns.x + ns.yyyy; 138 | vec4 h = 1.0 - abs(x) - abs(y); 139 | 140 | vec4 b0 = vec4( x.xy, y.xy ); 141 | vec4 b1 = vec4( x.zw, y.zw ); 142 | 143 | //vec4 s0 = vec4(lessThan(b0,0.0))*2.0 - 1.0; 144 | //vec4 s1 = vec4(lessThan(b1,0.0))*2.0 - 1.0; 145 | vec4 s0 = floor(b0)*2.0 + 1.0; 146 | vec4 s1 = floor(b1)*2.0 + 1.0; 147 | vec4 sh = -step(h, vec4(0.0)); 148 | 149 | vec4 a0 = b0.xzyw + s0.xzyw*sh.xxyy ; 150 | vec4 a1 = b1.xzyw + s1.xzyw*sh.zzww ; 151 | 152 | vec3 p0 = vec3(a0.xy,h.x); 153 | vec3 p1 = vec3(a0.zw,h.y); 154 | vec3 p2 = vec3(a1.xy,h.z); 155 | vec3 p3 = vec3(a1.zw,h.w); 156 | 157 | //Normalise gradients 158 | vec4 norm = taylorInvSqrt(vec4(dot(p0,p0), dot(p1,p1), dot(p2, p2), dot(p3,p3))); 159 | p0 *= norm.x; 160 | p1 *= norm.y; 161 | p2 *= norm.z; 162 | p3 *= norm.w; 163 | 164 | // Mix final noise value 165 | vec4 m = max(0.5 - vec4(dot(x0,x0), dot(x1,x1), dot(x2,x2), dot(x3,x3)), 0.0); 166 | m = m * m; 167 | return 105.0 * dot( m*m, vec4( dot(p0,x0), dot(p1,x1), 168 | dot(p2,x2), dot(p3,x3) ) ); 169 | } 170 | 171 | 172 | float random(float pos) { 173 | return fract(sin(pos) * 23567.7568245); 174 | } 175 | 176 | float random(vec2 pos) { 177 | return fract(sin(dot(pos, vec2(34.910385, 87.492755))) * 23567.7568245); 178 | } 179 | 180 | -------------------------------------------------------------------------------- /resources/shaders/post.glsl: -------------------------------------------------------------------------------- 1 | #pragma include "./fullscreen_quad.vert" 2 | 3 | #pragma shaderstage fragment 4 | #version 330 core 5 | 6 | #pragma include "/util.glsl" 7 | #pragma include "/noise.glsl" 8 | #pragma include "/adjustables.glsl" 9 | 10 | uniform sampler2D colorBuffer; 11 | uniform sampler2D depthBuffer; 12 | 13 | uniform uvec2 screenDimensions; 14 | uniform vec3 cameraPosWorld; 15 | uniform mat4 viewMatrix; 16 | uniform mat4 projectionMatrix; 17 | 18 | uniform uint elapsedSeconds; 19 | uniform float elapsedSubseconds; 20 | 21 | float elapsedTime() { 22 | return float(elapsedSeconds) + elapsedSubseconds; 23 | } 24 | 25 | in vec2 v_texcoord; 26 | in vec2 v_uv; 27 | out vec4 o_color; 28 | 29 | float fogFactorExp(float density, float t) { 30 | return clamp(1.0 - exp(-density * t), 0.0, 1.0); 31 | } 32 | 33 | float fogFactorExp2(float density, float t) { 34 | float n = density * t; 35 | return clamp(1.0 - exp(-(n * n)), 0.0, 1.0); 36 | } 37 | 38 | const highp float NOISE_GRANULARITY = 0.2/255.0; 39 | 40 | void main() { 41 | vec3 originalColor = texture2D(colorBuffer, v_texcoord).rgb; 42 | vec3 color = originalColor; 43 | float depth = 2.0 * texture2D(depthBuffer, v_texcoord).r - 1.0; 44 | 45 | vec2 uvClip = v_uv; 46 | 47 | vec4 clipPos = vec4(uvClip, depth, 1.0); 48 | vec4 viewPos = inverse(projectionMatrix) * clipPos; 49 | viewPos /= viewPos.w; 50 | 51 | vec3 worldPos = (inverse(viewMatrix) * viewPos).xyz; 52 | 53 | float distToSurface = length(worldPos - cameraPosWorld) / DAY_NIGHT(900.0, 900.0); 54 | float fogStrength = fogFactorExp(0.4, distToSurface); 55 | // float fogStrength = 0.0; 56 | 57 | vec3 fogColor = DAY_NIGHT(FOG_COLOR, FOG_COLOR_NIGHT); 58 | vec3 finalColor = mix(color, fogColor, fogStrength); 59 | finalColor += mix(-NOISE_GRANULARITY, NOISE_GRANULARITY, random(vec2(v_texcoord.x, v_texcoord.y + elapsedSubseconds))); 60 | 61 | o_color = vec4(finalColor, 1.0); 62 | } 63 | -------------------------------------------------------------------------------- /resources/shaders/sky.glsl: -------------------------------------------------------------------------------- 1 | 2 | #pragma include "./fullscreen_quad.vert" 3 | 4 | #pragma shaderstage fragment 5 | #version 330 core 6 | 7 | #pragma include "terrain/wind.glsl" 8 | #pragma include "/adjustables.glsl" 9 | 10 | uniform uvec2 screenDimensions; 11 | uniform vec3 cameraPosWorld; 12 | uniform mat4 projectionMatrix; 13 | uniform mat4 viewMatrix; 14 | 15 | uniform uint elapsedSeconds; 16 | uniform float elapsedSubseconds; 17 | 18 | float elapsedTime() { 19 | return float(elapsedSeconds) + elapsedSubseconds; 20 | } 21 | 22 | in vec2 v_texcoord; 23 | in vec2 v_uv; 24 | 25 | out vec4 b_color; 26 | 27 | #define UP (vec3(0.0, 1.0, 0.0)) 28 | 29 | struct Intersection { 30 | bool intersects; 31 | vec3 point; 32 | }; 33 | 34 | Intersection rayPlaneIntersection(vec3 rayOrigin, vec3 rayDir, vec3 planeNormal, float planeDistance) { 35 | // t = -(O . N - d) / (V . N) 36 | // P = O + -((O . N - d) / (V . N))V 37 | float t = (-dot(rayOrigin, planeNormal) + planeDistance) / dot(rayDir, planeNormal); 38 | if (t >= 0.0) { 39 | return Intersection(true, rayOrigin + t * rayDir); 40 | } 41 | 42 | return Intersection(false, vec3(0.0, 0.0, 0.0)); 43 | } 44 | 45 | float densityAt(vec3 v) { 46 | // v.xz /= 4.0; 47 | v.y -= cameraPosWorld.y; 48 | return max(0.0, cloudDensity(v, elapsedTime())); 49 | } 50 | 51 | void main() { 52 | vec3 intoScreen = -vec3(-v_uv, 1.0); 53 | intoScreen.x *= float(screenDimensions.x) / float(screenDimensions.y); 54 | // intoScreen *= 2.0; 55 | // intoScreen = fract(intoScreen); 56 | vec4 rayDirWorld = inverse(viewMatrix) * normalize(vec4(intoScreen, 0.0)); 57 | 58 | float downCloseness = pow(dot(rayDirWorld.xyz, UP), DAY_NIGHT(0.5, 0.2)); 59 | 60 | vec3 dayColor = mix(SKY_COLOR_BRIGHT, SKY_COLOR_BASE, max(0.0, downCloseness)); 61 | vec3 nightColor = mix(SKY_COLOR_NIGHT_BRIGHT, SKY_COLOR_NIGHT_BASE, max(0.0, downCloseness)); 62 | vec3 color = DAY_NIGHT(dayColor, nightColor); 63 | 64 | 65 | Intersection p = rayPlaneIntersection(cameraPosWorld.xyz, rayDirWorld.xyz, UP, cameraPosWorld.y + CLOUD_PLANE_DISTANCE); 66 | 67 | float distanceToEdge = length(p.point.xz - cameraPosWorld.xz); 68 | if (p.intersects && distanceToEdge <= CLOUD_PLANE_DISTANCE_CUTOFF) { 69 | float cloudFactor = 1.0; 70 | for (int i = 0; i < 1; ++i) { 71 | vec3 v = p.point + float(350 * i) * rayDirWorld.xyz; 72 | vec3 u = p.point + float(350 * (i + 1)) * rayDirWorld.xyz; 73 | 74 | float cloudIntensity = densityAt(v); 75 | cloudIntensity *= 1.0 - (distanceToEdge / CLOUD_PLANE_DISTANCE_CUTOFF); 76 | 77 | cloudFactor *= cloudIntensity; 78 | } 79 | 80 | vec3 cloudBaseDay = vec3(1.0); 81 | vec3 cloudBaseNight = 3.5 * SKY_COLOR_NIGHT_BASE; 82 | vec3 cloudBase = DAY_NIGHT(cloudBaseDay, cloudBaseNight); 83 | 84 | vec3 cloudIntenseDay = 0.9 * color; 85 | vec3 cloudIntenseNight = 1.5 * SKY_COLOR_NIGHT_BASE; 86 | vec3 cloudIntense = DAY_NIGHT(cloudIntenseDay, cloudIntenseNight); 87 | 88 | float intenseFactor = smoothstep(0.2, 1.0, 1.0 - pow(1.0 - cloudFactor, 2.1)); 89 | vec3 cloudColor = mix(cloudBase, cloudIntense, intenseFactor); 90 | cloudFactor = smoothstep(0.15, 0.2, cloudFactor); 91 | 92 | color = mix(color, cloudColor, cloudFactor); 93 | } 94 | 95 | b_color = vec4(color, 1.0); 96 | } 97 | -------------------------------------------------------------------------------- /resources/shaders/terrain/main.glsl: -------------------------------------------------------------------------------- 1 | #pragma include "unpack.glsl" 2 | 3 | #pragma shaderstage vertex 4 | #pragma include "wind.glsl" 5 | #pragma include "/util.glsl" 6 | 7 | uniform mat4 model; 8 | uniform mat4 view; 9 | uniform mat4 projection; 10 | 11 | out float vSkyLight; 12 | out float vBlockLight; 13 | out float vStaticBrightness; 14 | out vec3 vWorldPos; 15 | out vec2 vTextureUv; 16 | flat out int vTextureId; 17 | 18 | #define AO_MIN_BRIGHTNESS 0.3 19 | #define AO_ATTENUATION 1.5 20 | 21 | float contribution(bool contribute, float strength) { 22 | return 1.0 - float(contribute) * strength; 23 | } 24 | 25 | uniform uint elapsedSeconds; 26 | uniform float elapsedSubseconds; 27 | 28 | float elapsedTime() { 29 | return 2.0 * (float(elapsedSeconds) + elapsedSubseconds); 30 | } 31 | 32 | void main() { 33 | TerrainVertex vertex = unpackVertex(); 34 | 35 | vec3 worldPos = (model * vec4(vertex.modelPos, 1.0)).xyz; 36 | vWorldPos = worldPos; 37 | 38 | if (vertex.windSway) { 39 | worldPos.xz += windTotal(worldPos, elapsedTime()); 40 | } 41 | 42 | gl_Position = projection * view * vec4(worldPos, 1.0); 43 | 44 | float brightness = 1.0; 45 | 46 | float aoFactor = 1.0; 47 | aoFactor *= mix(AO_MIN_BRIGHTNESS, 1.0, vertex.ao); 48 | aoFactor = pow(aoFactor, AO_ATTENUATION); 49 | brightness *= aoFactor; 50 | 51 | // directional lighting 52 | float directionFactor = 1.0; 53 | directionFactor *= contribution(vertex.axis == AXIS_X, 0.3); // X 54 | directionFactor *= contribution(vertex.axis == AXIS_Y && vertex.axisSign == SIGN_POSITIVE, 0.0); // top 55 | directionFactor *= contribution(vertex.axis == AXIS_Y && vertex.axisSign == SIGN_NEGATIVE, 0.5); // bottom 56 | directionFactor *= contribution(vertex.axis == AXIS_Z, 0.4); // Z 57 | brightness *= directionFactor; 58 | 59 | vBlockLight = vertex.blockLight; 60 | vSkyLight = vertex.skyLight; 61 | vStaticBrightness = brightness; 62 | 63 | vTextureUv = vertex.textureCoordinates; 64 | vTextureId = vertex.textureId; 65 | } 66 | 67 | #pragma shaderstage fragment 68 | #version 330 core 69 | 70 | #pragma include "wind.glsl" 71 | #pragma include "/adjustables.glsl" 72 | 73 | uniform sampler2DArray albedo_maps; 74 | 75 | uniform uint elapsedSeconds; 76 | uniform float elapsedSubseconds; 77 | 78 | float elapsedTime() { 79 | return float(elapsedSeconds) + elapsedSubseconds; 80 | } 81 | 82 | in float vStaticBrightness; 83 | in float vBlockLight; 84 | in float vSkyLight; 85 | in vec2 vTextureUv; 86 | flat in int vTextureId; 87 | in vec3 vWorldPos; 88 | 89 | out vec3 b_color; 90 | 91 | // const highp float NOISE_GRANULARITY = 0.2/255.0; 92 | 93 | // higher values mean light "drops off" from the source more quickly. 94 | #define LIGHT_ATTENUATION 2.0 95 | 96 | // lower values mean that zero brightness is darker. 97 | #define LIGHT_MIN_BRIGHNESS 0.04 98 | 99 | void main() { 100 | vec4 fragmentColor = texture(albedo_maps, vec3(vTextureUv, vTextureId)); 101 | if (fragmentColor.a < 0.5) { 102 | discard; 103 | } 104 | 105 | float cloudFactor = 1.0 - smoothstep(0.15, 0.4, cloudDensity(vec3(vWorldPos.x, 1000.0, vWorldPos.z), elapsedTime())); 106 | cloudFactor = mix(0.3, 1.0, pow(cloudFactor, 8.0)); 107 | cloudFactor = mix(1.0, cloudFactor, vSkyLight); // [min, 1] 108 | 109 | float dayNightFactor = DAY_NIGHT_FACTOR(elapsedTime()); // [0, 1] 110 | 111 | float skyLightFactor = mix(LIGHT_MIN_BRIGHNESS, 1.0, pow(vSkyLight * DAY_NIGHT_FACTOR(elapsedTime()), LIGHT_ATTENUATION)); // [min, skyLight] 112 | float blockLightFactor = mix(LIGHT_MIN_BRIGHNESS, 1.0, pow(vBlockLight, LIGHT_ATTENUATION)); // [min, blockLight] 113 | 114 | float brightness = 0.0; 115 | 116 | // [bmin, blockLight] 117 | brightness = max(brightness, skyLightFactor); 118 | brightness *= cloudFactor; 119 | brightness = max(brightness, blockLightFactor); 120 | 121 | brightness *= vStaticBrightness; 122 | 123 | // if (cloudFactor > 0.001 && cloudFactor < 0.005) { 124 | // fragmentColor.rgb += vec3(1.0); 125 | // } else if (cloudFactor > 0.401 && cloudFactor < 0.405) { 126 | // fragmentColor.r = 1.0; 127 | // } else if (cloudFactor > 0.405) { 128 | // fragmentColor.r += 0.01; 129 | // } else if (cloudFactor > 0.005) { 130 | // fragmentColor.rgb += vec3(0.01); 131 | // } 132 | // fragmentColor.rgb = vec3(cloudFactor); 133 | fragmentColor.rgb *= brightness; 134 | // fragmentColor.rgb = vec3(vTextureUv / 32.0, 1.0); 135 | 136 | // apply some slight noise to mitigate banding in dark regions. 137 | // fragmentColor += mix(-NOISE_GRANULARITY, NOISE_GRANULARITY, random(vTextureUv)); 138 | 139 | b_color = fragmentColor.rgb; 140 | } 141 | -------------------------------------------------------------------------------- /resources/shaders/terrain/unpack.glsl: -------------------------------------------------------------------------------- 1 | #pragma shaderstage vertex 2 | #version 330 core 3 | 4 | #pragma include "/util.glsl" 5 | 6 | #define AXIS_X 0 7 | #define AXIS_Y 1 8 | #define AXIS_Z 2 9 | #define SIGN_POSITIVE 0 10 | #define SIGN_NEGATIVE 1 11 | 12 | in uint pos_ao; 13 | in uint light_flags_side_id; 14 | 15 | struct TerrainVertex { 16 | vec3 modelPos; 17 | vec3 modelNormal; 18 | int axis; 19 | int axisSign; 20 | 21 | int textureId; 22 | vec2 textureCoordinates; 23 | 24 | float blockLight; 25 | float skyLight; 26 | float ao; 27 | bool windSway; 28 | }; 29 | 30 | TerrainVertex unpackVertex() { 31 | vec3 normalTable[3]; 32 | float signTable[2]; 33 | 34 | normalTable = vec3[3]( 35 | vec3(1.0, 0.0, 0.0), 36 | vec3(0.0, 1.0, 0.0), 37 | vec3(0.0, 0.0, 1.0) 38 | ); 39 | signTable = float[2](1.0, -1.0); 40 | 41 | // unpack attributes 42 | float ao = float(BITS(pos_ao, 0, 2)) / 3.0; 43 | float z = float(BITS(pos_ao, 2, 10)) / 16.0; 44 | float y = float(BITS(pos_ao, 12, 10)) / 16.0; 45 | float x = float(BITS(pos_ao, 22, 10)) / 16.0; 46 | 47 | int textureId = int (BITS(light_flags_side_id, 0, 16)); 48 | int axis = int (BITS(light_flags_side_id, 16, 2)); 49 | int axisSign = int (BITS(light_flags_side_id, 18, 1)); 50 | bool windSway = bool (BITS(light_flags_side_id, 23, 1)); 51 | float blockLight = float(BITS(light_flags_side_id, 24, 4)) / 16.0; 52 | float skyLight = float(BITS(light_flags_side_id, 28, 4)) / 16.0; 53 | 54 | vec3 modelPos = vec3(x, y, z); 55 | vec3 modelNormal = normalTable[axis]; 56 | modelNormal *= signTable[axisSign]; 57 | 58 | vec2 uvTable[3]; 59 | uvTable = vec2[3]( 60 | vec2(z, y), 61 | vec2(x, z), 62 | vec2(x, y) 63 | ); 64 | vec2 textureCoordinates = uvTable[axis]; 65 | 66 | 67 | return TerrainVertex( 68 | modelPos, 69 | modelNormal, 70 | axis, 71 | axisSign, 72 | textureId, 73 | textureCoordinates, 74 | blockLight, 75 | skyLight, 76 | ao, 77 | windSway 78 | ); 79 | } 80 | 81 | -------------------------------------------------------------------------------- /resources/shaders/terrain/wind.glsl: -------------------------------------------------------------------------------- 1 | #pragma include "/noise.glsl" 2 | #pragma include "/adjustables.glsl" 3 | 4 | #define WIND_INTENSITY_SCROLL_DIRECTION vec2(1.0, 0.1) 5 | #define WIND_INTENSITY_SCROLL_SPEED 3.0 6 | 7 | vec2 windDirection(vec3 worldPos, float t) { 8 | vec2 windDirection = vec2(0.0, 0.0); 9 | windDirection.x = simplexNoise(0.001 * vec3(worldPos.xz, t)); 10 | windDirection.y = simplexNoise(0.001 * vec3(t, worldPos.xz)); 11 | return windDirection; 12 | } 13 | 14 | float remap01(float t) { 15 | return 0.5 * t + 0.5; 16 | } 17 | 18 | struct NoiseParameters { 19 | int octaveCount; 20 | float baseFrequency; 21 | float frequencyScale; 22 | float baseAmplitude; 23 | float amplitudeScale; 24 | float scrollSpeed; 25 | float bubbleSpeed; 26 | }; 27 | 28 | float calculateNoise(NoiseParameters params, vec3 worldPos, float t) { 29 | float frequency = params.baseFrequency; 30 | float amplitude = params.baseAmplitude; 31 | float total = 0.0; 32 | float maxTotal = 0.0; 33 | 34 | for (int octave = 0; octave < params.octaveCount; ++octave) { 35 | vec3 motionDirection = vec3(0.0, 0.0, 0.0); 36 | motionDirection.x = random(float(octave) / float(params.octaveCount)); 37 | motionDirection.z = random(float(octave) / float(params.octaveCount)); 38 | motionDirection.y = params.bubbleSpeed / float(octave + 1); 39 | 40 | vec3 scrollOffset = params.scrollSpeed * t * motionDirection; 41 | vec3 pos = worldPos + scrollOffset; 42 | 43 | // can almost think of this as a map of wind pockets that scroll over the landscape over time 44 | total += amplitude * simplexNoise(frequency * pos); 45 | maxTotal += amplitude; 46 | 47 | frequency *= params.frequencyScale; 48 | amplitude *= params.amplitudeScale; 49 | } 50 | 51 | return total / maxTotal; 52 | } 53 | 54 | float cloudDensity(vec3 worldPos, float t) { 55 | NoiseParameters params = CLOUD_NOISE_PARAMS; 56 | float densityNoise = calculateNoise(params, worldPos, t); 57 | 58 | vec3 coverageOffset = vec3(0.0, 0.0, 0.0); 59 | coverageOffset.xz = CLOUD_NOISE_COVERAGE_SCROLL_DIRECTION; 60 | coverageOffset.xz *= CLOUD_NOISE_COVERAGE_SCROLL_SPEED * t; 61 | float coverageNoise = simplexNoise(CLOUD_NOISE_COVERAGE_SCALE * (worldPos + vec3(coverageOffset.x, 0.0, coverageOffset.y))); 62 | coverageNoise = smoothstep(-1.0, 1.0, coverageNoise); 63 | // return coverageNoise; 64 | 65 | return max(1.0 - coverageNoise, 0.5 * densityNoise + 0.5) - (1.0 - coverageNoise); 66 | } 67 | 68 | float windIntensity(vec3 worldPos, float t) { 69 | return max(0.0, cloudDensity(worldPos, t)); 70 | } 71 | 72 | float windLocalStrength(vec3 worldPos, float t) { 73 | return 0.5 + 0.5 * simplexNoise(vec2(worldPos.x + t, worldPos.z - 0.5 * t)); 74 | } 75 | 76 | vec2 windTotal(vec3 worldPos, float t) { 77 | vec2 direction = windDirection(worldPos, t); 78 | float localStrength = windLocalStrength(worldPos, t); 79 | float intensity = windIntensity(worldPos, t); 80 | 81 | return mix(0.1, 1.0, intensity) * (localStrength * direction); 82 | } 83 | -------------------------------------------------------------------------------- /resources/shaders/util.glsl: -------------------------------------------------------------------------------- 1 | #define PI 3.1415926535 2 | #define BITS(packed, a, n) ((packed) >> (a)) & uint((1 << (n)) - 1) 3 | -------------------------------------------------------------------------------- /resources/textures/blocks/detail_medium_grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/detail_medium_grass.png -------------------------------------------------------------------------------- /resources/textures/blocks/detail_short_grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/detail_short_grass.png -------------------------------------------------------------------------------- /resources/textures/blocks/dirt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/dirt.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_side.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_side.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_variant_1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_variant_1.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_variant_2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_variant_2.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_variant_3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_variant_3.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_variant_4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_variant_4.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_variant_5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_variant_5.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_variant_6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_variant_6.png -------------------------------------------------------------------------------- /resources/textures/blocks/grass_variant_7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/grass_variant_7.png -------------------------------------------------------------------------------- /resources/textures/blocks/sand.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/sand.png -------------------------------------------------------------------------------- /resources/textures/blocks/stone.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/stone.png -------------------------------------------------------------------------------- /resources/textures/blocks/unknown.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/unknown.png -------------------------------------------------------------------------------- /resources/textures/blocks/water.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/blocks/water.png -------------------------------------------------------------------------------- /resources/textures/crosshair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/resources/textures/crosshair.png -------------------------------------------------------------------------------- /rustfmt.toml: -------------------------------------------------------------------------------- 1 | merge_imports = true 2 | reorder_impl_items = true 3 | use_field_init_shorthand = true 4 | wrap_comments = true 5 | overflow_delimited_expr = true 6 | -------------------------------------------------------------------------------- /source_textures/blocks/detail_medium_grass.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/detail_medium_grass.xcf -------------------------------------------------------------------------------- /source_textures/blocks/detail_short_grass.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/detail_short_grass.xcf -------------------------------------------------------------------------------- /source_textures/blocks/dirt.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/dirt.xcf -------------------------------------------------------------------------------- /source_textures/blocks/grass.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/grass.xcf -------------------------------------------------------------------------------- /source_textures/blocks/grass_side.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/grass_side.xcf -------------------------------------------------------------------------------- /source_textures/blocks/sand.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/sand.xcf -------------------------------------------------------------------------------- /source_textures/blocks/stone.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/stone.xcf -------------------------------------------------------------------------------- /source_textures/blocks/unknown.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/unknown.xcf -------------------------------------------------------------------------------- /source_textures/blocks/water.xcf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/XavilPergis/Notcraft/4d7c98ab2d3e92b480d15745f5cfe2e3954c4ed6/source_textures/blocks/water.xcf --------------------------------------------------------------------------------