diff --git a/Cargo.lock b/Cargo.lock index 76c15ad699..3de0c8fd88 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -6560,6 +6560,7 @@ dependencies = [ "serde", "tracing", "walkdir 2.3.2", + "wavefront", ] [[package]] @@ -7340,6 +7341,15 @@ dependencies = [ "wast", ] +[[package]] +name = "wavefront" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "249b7e6cd5bd1cc78a61d0475e5790c98bebabf2dc644a94a51ad58b39298652" +dependencies = [ + "hashbrown 0.9.1", +] + [[package]] name = "wayland-client" version = "0.28.6" diff --git a/assets/voxygen/lod/tree.mtl b/assets/voxygen/lod/tree.mtl new file mode 100644 index 0000000000..4f81bf8521 --- /dev/null +++ b/assets/voxygen/lod/tree.mtl @@ -0,0 +1,32 @@ +# Blender MTL File: 'None' +# Material Count: 3 + +newmtl Material +Ns 323.999994 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 + +newmtl Material.006 +Ns 225.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 + +newmtl Material.009 +Ns 225.000000 +Ka 1.000000 1.000000 1.000000 +Kd 0.800000 0.800000 0.800000 +Ks 0.500000 0.500000 0.500000 +Ke 0.000000 0.000000 0.000000 +Ni 1.450000 +d 1.000000 +illum 2 diff --git a/assets/voxygen/lod/tree.obj b/assets/voxygen/lod/tree.obj new file mode 100644 index 0000000000..584b49c6bf --- /dev/null +++ b/assets/voxygen/lod/tree.obj @@ -0,0 +1,244 @@ +# Blender v3.0.0 OBJ File: '' +# www.blender.org +o Cube +v 1.000000 1.000000 1.000000 +v 1.000000 1.000000 -1.000000 +v 1.000000 -1.000000 1.000000 +v 1.000000 -1.000000 -1.000000 +v -1.000000 1.000000 1.000000 +v -1.000000 1.000000 -1.000000 +v -1.000000 -1.000000 1.000000 +v -1.000000 -1.000000 -1.000000 +vt 0.875000 0.500000 +vt 0.625000 0.750000 +vt 0.625000 0.500000 +vt 0.375000 1.000000 +vt 0.375000 0.750000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.375000 0.000000 +vt 0.375000 0.500000 +vt 0.125000 0.750000 +vt 0.125000 0.500000 +vt 0.625000 0.250000 +vt 0.875000 0.750000 +vt 0.625000 1.000000 +vn 0.0000 0.0000 1.0000 +vn 0.0000 -1.0000 0.0000 +vn -1.0000 0.0000 0.0000 +vn 0.0000 0.0000 -1.0000 +vn 1.0000 0.0000 0.0000 +vn 0.0000 1.0000 0.0000 +s off +f 5/1/1 3/2/1 1/3/1 +f 3/2/2 8/4/2 4/5/2 +f 7/6/3 6/7/3 8/8/3 +f 2/9/4 8/10/4 6/11/4 +f 1/3/5 4/5/5 2/9/5 +f 5/12/6 2/9/6 6/7/6 +f 5/1/1 7/13/1 3/2/1 +f 3/2/2 7/14/2 8/4/2 +f 7/6/3 5/12/3 6/7/3 +f 2/9/4 4/5/4 8/10/4 +f 1/3/5 3/2/5 4/5/5 +f 5/12/6 1/3/6 2/9/6 +o Cube.001 +v -1.814604 0.161418 -2.709981 +v -1.359475 0.002174 10.636601 +v 0.002174 1.363824 10.636601 +v 0.161417 2.137439 -2.709981 +v 1.363824 0.002174 10.636601 +v 2.137438 0.161418 -2.709981 +v 0.002174 -1.359475 10.636601 +v 0.161417 -1.814604 -2.709981 +v -0.028484 -9.424881 9.196677 +v -0.028485 -4.126693 23.008556 +v -4.223946 0.068768 23.008556 +v -9.522135 0.068768 9.196677 +v -0.028485 4.264229 23.008556 +v -0.028484 9.562418 9.196677 +v 4.166976 0.068767 23.008556 +v 9.465164 0.068769 9.196677 +v 6.263892 0.068768 10.372999 +v -0.028484 6.361145 10.372999 +v -0.028484 6.638610 21.179264 +v 6.541358 0.068768 21.179264 +v -0.028485 -6.501075 21.179264 +v -6.598328 0.068768 21.179264 +v -0.028485 -5.165573 30.045181 +v 5.205855 0.068768 30.045181 +v 2.329653 0.068768 37.773296 +v -0.028485 -2.289370 37.773296 +v -3.490429 0.068768 31.783909 +v -0.028485 -3.393177 31.783909 +v -5.262825 0.068767 30.045181 +v 3.433459 0.068768 31.783909 +v -0.028485 3.530712 31.783909 +v -0.028485 5.303108 30.045181 +v -0.217867 -0.120616 46.893818 +v -2.386622 0.068767 37.773296 +v -0.028485 2.426905 37.773296 +v -6.320862 0.068768 10.372999 +v -0.028484 -6.223608 10.372999 +vt 0.375000 0.250000 +vt 0.625000 0.500000 +vt 0.375000 0.500000 +vt 0.375000 0.750000 +vt 0.625000 0.750000 +vt 0.375000 1.000000 +vt 0.125000 0.750000 +vt 0.125000 0.500000 +vt 0.625000 0.000000 +vt 0.375000 0.000000 +vt 0.625000 0.250000 +vt 0.625000 1.000000 +vt 0.625000 0.000000 +vt 0.375000 0.250000 +vt 0.375000 0.000000 +vt 0.625000 0.250000 +vt 0.375000 0.500000 +vt 0.625000 0.500000 +vt 0.375000 0.750000 +vt 0.375000 0.750000 +vt 0.375000 0.500000 +vt 0.625000 0.750000 +vt 0.625000 0.500000 +vt 0.625000 0.750000 +vt 0.625000 1.000000 +vt 0.375000 1.000000 +vt 0.625000 0.250000 +vt 0.625000 1.000000 +vt 0.625000 1.000000 +vt 0.625000 0.750000 +vt 0.625000 1.000000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.625000 0.250000 +vt 0.625000 0.750000 +vt 0.625000 1.000000 +vt 0.625000 0.500000 +vt 0.625000 0.500000 +vt 0.625000 1.000000 +vt 0.625000 0.500000 +vt 0.625000 0.750000 +vt 0.625000 0.000000 +vt 0.625000 0.250000 +vt 0.625000 0.500000 +vt 0.625000 0.750000 +vt 0.125000 0.750000 +vt 0.125000 0.500000 +vt 0.375000 0.250000 +vt 0.375000 0.000000 +vt 0.625000 0.250000 +vt 0.625000 0.000000 +vt 0.625000 0.000000 +vt 0.375000 1.000000 +vn -0.7067 0.7067 0.0325 +vn 0.7062 0.7062 0.0494 +vn 0.7067 -0.7067 0.0325 +vn 0.0000 0.0000 -1.0000 +vn -0.7070 -0.7070 0.0157 +vn -0.6824 -0.6824 0.2618 +vn -0.6824 0.6824 0.2618 +vn 0.6824 0.6824 0.2618 +vn -0.3261 -0.3261 -0.8873 +vn -0.5209 -0.5210 -0.6762 +vn 0.6824 -0.6824 0.2618 +vn 0.5209 0.5210 -0.6762 +vn 0.5210 0.5209 -0.6762 +vn -0.5210 0.5209 -0.6762 +vn -0.5210 0.5210 -0.6762 +vn -0.5209 0.5210 -0.6762 +vn 0.5210 -0.5210 -0.6762 +vn 0.5209 -0.5210 -0.6762 +vn 0.6838 -0.6838 0.2545 +vn 0.6894 -0.6894 0.2227 +vn 0.6895 -0.6895 0.2219 +vn 0.5736 0.5736 -0.5847 +vn -0.5736 0.5736 -0.5847 +vn 0.5736 -0.5736 -0.5847 +vn 0.6956 -0.6956 0.1798 +vn -0.6838 0.6838 0.2545 +vn -0.6894 0.6894 0.2227 +vn 0.6838 0.6838 0.2545 +vn 0.6874 0.6874 0.2342 +vn -0.6838 -0.6838 0.2545 +vn -0.6913 -0.6913 0.2102 +vn -0.6895 0.6895 0.2219 +vn -0.6956 0.6956 0.1798 +vn 0.6917 0.6917 0.2076 +vn -0.3261 0.3261 -0.8873 +vn 0.3261 -0.3261 -0.8873 +vn 0.3261 0.3261 -0.8873 +vn -0.5736 -0.5736 -0.5847 +vn -0.6989 -0.6989 0.1517 +vn 0.6924 0.6924 0.2029 +vn -0.6924 0.6924 0.2029 +vn 0.6924 -0.6924 0.2029 +vn -0.6924 -0.6924 0.2029 +vn 0.5210 -0.5209 -0.6762 +s 1 +f 9/15/7 11/16/7 12/17/7 +f 11/16/8 14/18/8 12/17/8 +f 13/19/9 16/20/9 14/18/9 +f 12/17/10 16/21/10 9/22/10 +f 15/23/11 9/15/11 16/24/11 +f 9/15/7 10/25/7 11/16/7 +f 11/16/8 13/19/8 14/18/8 +f 13/19/9 15/26/9 16/20/9 +f 12/17/10 14/18/10 16/21/10 +f 15/23/11 10/25/11 9/15/11 +f 18/27/12 20/28/12 17/29/12 +f 19/30/13 22/31/13 20/28/13 +f 21/32/14 24/33/14 22/31/14 +f 22/31/15 25/34/15 26/35/15 +f 23/36/16 27/37/16 28/38/16 +f 24/33/17 18/39/17 17/40/17 +f 18/27/18 30/41/19 19/30/19 +f 23/36/20 29/42/21 18/39/22 +f 19/30/23 27/37/24 21/32/24 +f 31/43/25 33/44/26 34/45/27 +f 36/46/28 37/47/28 35/48/28 +f 38/49/29 31/43/29 36/50/29 +f 35/48/30 40/51/30 39/52/30 +f 34/45/27 33/44/26 41/53/31 +f 37/47/32 43/54/33 40/51/32 +f 40/51/34 33/44/35 32/55/34 +f 37/47/36 34/56/37 42/57/37 +f 42/57/38 41/58/39 43/54/33 +f 43/54/35 41/59/40 33/44/35 +f 26/35/10 45/60/10 44/61/10 +f 17/40/41 25/34/41 24/33/41 +f 20/28/42 26/35/42 44/62/42 +f 20/28/43 45/63/43 17/29/43 +f 39/52/44 32/55/44 38/49/44 +f 34/56/37 41/64/45 42/57/37 +f 27/37/46 38/49/46 28/38/46 +f 30/41/47 39/52/47 27/37/47 +f 28/38/48 36/50/48 29/42/48 +f 29/65/49 35/48/49 30/41/49 +f 18/27/12 19/30/12 20/28/12 +f 19/30/13 21/32/13 22/31/13 +f 21/32/14 23/36/14 24/33/14 +f 22/31/15 24/33/15 25/34/15 +f 23/36/16 21/32/16 27/37/16 +f 24/33/17 23/36/17 18/39/17 +f 18/27/18 29/65/18 30/41/19 +f 23/36/20 28/38/20 29/42/21 +f 19/30/23 30/41/50 27/37/24 +f 31/43/25 32/55/25 33/44/26 +f 36/46/28 31/66/28 37/47/28 +f 38/49/29 32/55/29 31/43/29 +f 35/48/30 37/47/30 40/51/30 +f 37/47/32 42/57/38 43/54/33 +f 40/51/34 43/54/35 33/44/35 +f 37/47/36 31/66/36 34/56/37 +f 26/35/10 25/34/10 45/60/10 +f 17/40/41 45/67/41 25/34/41 +f 20/28/42 22/31/42 26/35/42 +f 20/28/43 44/62/43 45/63/43 +f 39/52/44 40/51/44 32/55/44 +f 27/37/46 39/52/46 38/49/46 +f 30/41/47 35/48/47 39/52/47 +f 28/38/48 38/49/48 36/50/48 +f 29/65/49 36/46/49 35/48/49 diff --git a/assets/voxygen/shaders/lod-object-vert.glsl b/assets/voxygen/shaders/lod-object-vert.glsl new file mode 100644 index 0000000000..d585640c04 --- /dev/null +++ b/assets/voxygen/shaders/lod-object-vert.glsl @@ -0,0 +1,43 @@ +#version 420 core + +#include + +#define LIGHTING_TYPE LIGHTING_TYPE_REFLECTION + +#define LIGHTING_REFLECTION_KIND LIGHTING_REFLECTION_KIND_GLOSSY + +#define LIGHTING_TRANSPORT_MODE LIGHTING_TRANSPORT_MODE_IMPORTANCE + +#define LIGHTING_DISTRIBUTION_SCHEME LIGHTING_DISTRIBUTION_SCHEME_MICROFACET + +#define LIGHTING_DISTRIBUTION LIGHTING_DISTRIBUTION_BECKMANN + +#include +#include +#include +#include + +layout(location = 0) in vec3 v_pos; +layout(location = 1) in vec3 v_norm; +layout(location = 2) in vec3 v_col; +layout(location = 3) in vec3 inst_pos; + +layout(location = 0) out vec3 f_pos; +layout(location = 1) out vec3 f_norm; +layout(location = 2) out vec4 f_col; +layout(location = 3) out float f_reflect; + +void main() { + f_pos = inst_pos + v_pos - focus_off.xyz; + + float pull_down = 1.0 / pow(distance(focus_pos.xy, f_pos.xy) / (view_distance.x * 0.95), 20.0); + f_pos.z -= pull_down; + + f_norm = v_norm; + f_col = vec4(vec3(0, 0.2, 0), 1.0);//vec4(v_col, 1.0); + f_reflect = 1.0; + + gl_Position = + all_mat * + vec4(f_pos, 1); +} diff --git a/client/src/lib.rs b/client/src/lib.rs index e4769b5381..1c5ebd26c2 100644 --- a/client/src/lib.rs +++ b/client/src/lib.rs @@ -46,6 +46,8 @@ use common::{ trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult}, uid::{Uid, UidAllocator}, vol::RectVolSize, + spiral::Spiral2d, + lod, }; use common_base::{prof_span, span}; use common_net::{ @@ -171,10 +173,12 @@ pub struct Client { pub chat_mode: ChatMode, recipe_book: RecipeBook, available_recipes: HashMap>, + lod_zones: HashMap, lod::Zone>, + lod_last_requested: Option, max_group_size: u32, // Client has received an invite (inviter uid, time out instant) - invite: Option<(Uid, std::time::Instant, std::time::Duration, InviteKind)>, + invite: Option<(Uid, Instant, Duration, InviteKind)>, group_leader: Option, // Note: potentially representable as a client only component group_members: HashMap, @@ -202,6 +206,7 @@ pub struct Client { state: State, view_distance: Option, + lod_distance: u32, // TODO: move into voxygen loaded_distance: f32, @@ -624,6 +629,9 @@ impl Client { available_recipes: HashMap::default(), chat_mode: ChatMode::default(), + lod_zones: HashMap::new(), + lod_last_requested: None, + max_group_size, invite: None, group_leader: None, @@ -650,6 +658,7 @@ impl Client { tick: 0, state, view_distance: None, + lod_distance: 2, // TODO: Make configurable loaded_distance: 0.0, pending_chunks: HashMap::new(), @@ -769,7 +778,8 @@ impl Client { &mut self.in_game_stream }, //Only in game, terrain - ClientGeneral::TerrainChunkRequest { .. } => { + ClientGeneral::TerrainChunkRequest { .. } + | ClientGeneral::LodZoneRequest { .. } => { #[cfg(feature = "tracy")] { terrain = 1.0; @@ -994,6 +1004,10 @@ impl Client { &self.available_recipes } + pub fn lod_zones(&self) -> &HashMap, lod::Zone> { + &self.lod_zones + } + /// Returns whether the specified recipe can be crafted and the sprite, if /// any, that is required to do so. pub fn can_craft_recipe(&self, recipe: &str) -> (bool, Option) { @@ -1709,6 +1723,27 @@ impl Client { let now = Instant::now(); self.pending_chunks .retain(|_, created| now.duration_since(*created) < Duration::from_secs(3)); + + // Manage LoD zones + let lod_zone = pos.0.xy().map(|e| lod::from_wpos(e as i32)); + + // Request LoD zones that are in range + if self.lod_last_requested.map_or(true, |i| i.elapsed() > Duration::from_secs(5)) { + + if let Some(unloaded) = Spiral2d::new() + .take((1 + self.lod_distance * 2).pow(2) as usize) + .map(|rpos| lod_zone + rpos) + .find(|p| !self.lod_zones.contains_key(p)) + { + self.send_msg_err(ClientGeneral::LodZoneRequest { + key: unloaded, + })?; + self.lod_last_requested = Some(Instant::now()); + } + } + + // Cull LoD zones out of range + self.lod_zones.retain(|p, _| (*p - lod_zone).map(i32::abs).reduce_max() < self.lod_distance as i32 + 1); } Ok(()) @@ -2084,6 +2119,10 @@ impl Client { } self.pending_chunks.remove(&key); }, + ServerGeneral::LodZoneUpdate { key, zone } => { + self.lod_zones.insert(key, zone); + self.lod_last_requested = None; + }, ServerGeneral::TerrainBlockUpdates(blocks) => { if let Some(mut blocks) = blocks.decompress() { blocks.drain().for_each(|(pos, block)| { diff --git a/common/assets/Cargo.toml b/common/assets/Cargo.toml index 3c11ceaae7..1b59285fed 100644 --- a/common/assets/Cargo.toml +++ b/common/assets/Cargo.toml @@ -10,6 +10,7 @@ lazy_static = "1.4.0" assets_manager = {version = "0.7", features = ["bincode", "ron", "json"]} ron = { version = "0.7", default-features = false } dot_vox = "4.0" +wavefront = "0.2" image = { version = "0.23.12", default-features = false, features = ["png"] } tracing = "0.1" diff --git a/common/assets/src/lib.rs b/common/assets/src/lib.rs index 594edc6864..4784de231e 100644 --- a/common/assets/src/lib.rs +++ b/common/assets/src/lib.rs @@ -191,6 +191,22 @@ impl Asset for DotVoxAsset { const EXTENSION: &'static str = "vox"; } +pub struct ObjAsset(pub wavefront::Obj); + +impl Asset for ObjAsset { + type Loader = ObjAssetLoader; + + const EXTENSION: &'static str = "obj"; +} + +pub struct ObjAssetLoader; +impl Loader for ObjAssetLoader { + fn load(content: std::borrow::Cow<[u8]>, _: &str) -> Result { + let data = wavefront::Obj::from_reader(&*content)?; + Ok(ObjAsset(data)) + } +} + /// Return path to repository root by searching 10 directories back pub fn find_root() -> Option { std::env::current_dir().map_or(None, |path| { diff --git a/common/net/src/msg/client.rs b/common/net/src/msg/client.rs index 9a7b9495b0..9f310dd962 100644 --- a/common/net/src/msg/client.rs +++ b/common/net/src/msg/client.rs @@ -83,6 +83,9 @@ pub enum ClientGeneral { TerrainChunkRequest { key: Vec2, }, + LodZoneRequest { + key: Vec2, + }, //Always possible ChatMsg(String), Command(String, Vec), @@ -128,6 +131,7 @@ impl ClientMsg { | ClientGeneral::ExitInGame | ClientGeneral::PlayerPhysics { .. } | ClientGeneral::TerrainChunkRequest { .. } + | ClientGeneral::LodZoneRequest { .. } | ClientGeneral::UnlockSkill(_) | ClientGeneral::RequestSiteInfo(_) | ClientGeneral::UnlockSkillGroup(_) diff --git a/common/net/src/msg/server.rs b/common/net/src/msg/server.rs index 04d413b633..d8174d2d7c 100644 --- a/common/net/src/msg/server.rs +++ b/common/net/src/msg/server.rs @@ -13,6 +13,7 @@ use common::{ terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, trade::{PendingTrade, SitePrices, TradeId, TradeResult}, uid::Uid, + lod, }; use hashbrown::HashMap; use serde::{Deserialize, Serialize}; @@ -169,6 +170,10 @@ pub enum ServerGeneral { key: Vec2, chunk: Result, }, + LodZoneUpdate { + key: Vec2, + zone: lod::Zone, + }, TerrainBlockUpdates(CompressedData, Block>>), // Always possible PlayerListUpdate(PlayerListUpdate), @@ -293,6 +298,7 @@ impl ServerMsg { | ServerGeneral::ExitInGameSuccess | ServerGeneral::InventoryUpdate(_, _) | ServerGeneral::TerrainChunkUpdate { .. } + | ServerGeneral::LodZoneUpdate { .. } | ServerGeneral::TerrainBlockUpdates(_) | ServerGeneral::SetViewDistance(_) | ServerGeneral::Outcomes(_) diff --git a/common/src/lib.rs b/common/src/lib.rs index ac0470bff2..e1c0a036e8 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -49,6 +49,7 @@ pub mod figure; pub mod generation; #[cfg(not(target_arch = "wasm32"))] pub mod grid; #[cfg(not(target_arch = "wasm32"))] pub mod link; +#[cfg(not(target_arch = "wasm32"))] pub mod lod; #[cfg(not(target_arch = "wasm32"))] pub mod lottery; #[cfg(not(target_arch = "wasm32"))] diff --git a/common/src/lod.rs b/common/src/lod.rs new file mode 100644 index 0000000000..3ccc344c4e --- /dev/null +++ b/common/src/lod.rs @@ -0,0 +1,35 @@ +use vek::*; +use serde::{Serialize, Deserialize}; +use strum::EnumIter; +use crate::{ + terrain::TerrainChunkSize, + vol::RectVolSize, +}; + +// In chunks +pub const ZONE_SIZE: u32 = 64; + +#[derive(Copy, Clone, Hash, PartialEq, Eq, Debug, Serialize, Deserialize, EnumIter)] +#[repr(u16)] +pub enum ObjectKind { + Tree, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Object { + pub kind: ObjectKind, + pub pos: Vec3, +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +pub struct Zone { + pub objects: Vec, +} + +pub fn to_wpos(wpos: i32) -> i32 { + wpos * (TerrainChunkSize::RECT_SIZE.x * ZONE_SIZE) as i32 +} + +pub fn from_wpos(zone_pos: i32) -> i32 { + zone_pos / (TerrainChunkSize::RECT_SIZE.x * ZONE_SIZE) as i32 +} diff --git a/server/src/client.rs b/server/src/client.rs index f917b0fa46..d357d3d4e1 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -118,6 +118,7 @@ impl Client { }, //Ingame related, terrain ServerGeneral::TerrainChunkUpdate { .. } + | ServerGeneral::LodZoneUpdate { .. } | ServerGeneral::TerrainBlockUpdates(_) => { self.terrain_stream.lock().unwrap().send(g) }, @@ -191,6 +192,7 @@ impl Client { }, //Ingame related, terrain ServerGeneral::TerrainChunkUpdate { .. } + | ServerGeneral::LodZoneUpdate { .. } | ServerGeneral::TerrainBlockUpdates(_) => { PreparedMsg::new(5, &g, &self.terrain_stream_params) }, diff --git a/server/src/lib.rs b/server/src/lib.rs index 0f3600ce95..ad4fb55e9c 100644 --- a/server/src/lib.rs +++ b/server/src/lib.rs @@ -24,6 +24,7 @@ pub mod error; pub mod events; pub mod input; pub mod location; +pub mod lod; pub mod login_provider; pub mod metrics; pub mod persistence; @@ -449,6 +450,7 @@ impl Server { // Insert the world into the ECS (todo: Maybe not an Arc?) let world = Arc::new(world); state.ecs_mut().insert(Arc::clone(&world)); + state.ecs_mut().insert(lod::Lod::from_world(&world, index.as_index_ref())); state.ecs_mut().insert(index.clone()); // Set starting time for the server. diff --git a/server/src/lod.rs b/server/src/lod.rs new file mode 100644 index 0000000000..9a7a02f8ed --- /dev/null +++ b/server/src/lod.rs @@ -0,0 +1,33 @@ +use common::lod; +use world::World; +use hashbrown::HashMap; +use vek::*; + +static EMPTY_ZONE: lod::Zone = lod::Zone { + objects: Vec::new(), +}; + +pub struct Lod { + pub zones: HashMap, lod::Zone>, +} + +impl Lod { + pub fn from_world(world: &World, index: world::IndexRef) -> Self { + let mut zones = HashMap::new(); + + let zone_sz = (world.sim().get_size() + lod::ZONE_SIZE - 1) / lod::ZONE_SIZE; + + for i in 0..zone_sz.x { + for j in 0..zone_sz.y { + let zone_pos = Vec2::new(i, j).map(|e| e as i32); + zones.insert(zone_pos, world.get_lod_zone(zone_pos, index)); + } + } + + Self { zones } + } + + pub fn zone(&self, zone_pos: Vec2) -> &lod::Zone { + self.zones.get(&zone_pos).unwrap_or(&EMPTY_ZONE) + } +} diff --git a/server/src/sys/msg/in_game.rs b/server/src/sys/msg/in_game.rs index 0b1972eb1b..6ceb6da12a 100644 --- a/server/src/sys/msg/in_game.rs +++ b/server/src/sys/msg/in_game.rs @@ -295,6 +295,7 @@ impl Sys { | ClientGeneral::Character(_) | ClientGeneral::Spectate | ClientGeneral::TerrainChunkRequest { .. } + | ClientGeneral::LodZoneRequest { .. } | ClientGeneral::ChatMsg(_) | ClientGeneral::Command(..) | ClientGeneral::Terminate => { diff --git a/server/src/sys/msg/terrain.rs b/server/src/sys/msg/terrain.rs index e6b39c1808..d806e36ca9 100644 --- a/server/src/sys/msg/terrain.rs +++ b/server/src/sys/msg/terrain.rs @@ -1,4 +1,4 @@ -use crate::{client::Client, metrics::NetworkRequestMetrics, presence::Presence, ChunkRequest}; +use crate::{client::Client, lod::Lod, metrics::NetworkRequestMetrics, presence::Presence, ChunkRequest}; use common::{ comp::Pos, event::{EventBus, ServerEvent}, @@ -20,6 +20,7 @@ impl<'a> System<'a> for Sys { Entities<'a>, Read<'a, EventBus>, ReadExpect<'a, TerrainGrid>, + ReadExpect<'a, Lod>, ReadExpect<'a, NetworkRequestMetrics>, Write<'a, Vec>, ReadStorage<'a, Pos>, @@ -37,6 +38,7 @@ impl<'a> System<'a> for Sys { entities, server_event_bus, terrain, + lod, network_metrics, mut chunk_requests, positions, @@ -101,6 +103,12 @@ impl<'a> System<'a> for Sys { network_metrics.chunks_request_dropped.inc(); } }, + ClientGeneral::LodZoneRequest { key } => { + client.send(ServerGeneral::LodZoneUpdate { + key, + zone: lod.zone(key).clone(), + })?; + }, _ => { debug!( "Kicking possibly misbehaving client due to invalud terrain \ diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index 0a7a9556fc..c2d021f14b 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -35,6 +35,10 @@ pub use self::{ Instance as SpriteInstance, SpriteGlobalsBindGroup, SpriteVerts, Vertex as SpriteVertex, VERT_PAGE_SIZE as SPRITE_VERT_PAGE_SIZE, }, + lod_object::{ + Instance as LodObjectInstance, + Vertex as LodObjectVertex, + }, terrain::{Locals as TerrainLocals, TerrainLayout, Vertex as TerrainVertex}, trail::Vertex as TrailVertex, ui::{ diff --git a/voxygen/src/render/pipelines/lod_object.rs b/voxygen/src/render/pipelines/lod_object.rs new file mode 100644 index 0000000000..e08040a24d --- /dev/null +++ b/voxygen/src/render/pipelines/lod_object.rs @@ -0,0 +1,167 @@ +use super::{ + super::{ + buffer::Buffer, AaMode, GlobalsLayouts, Mesh, TerrainLayout, Texture, Vertex as VertexTrait, + }, + lod_terrain, GlobalModel, +}; +use bytemuck::{Pod, Zeroable}; +use std::mem; +use vek::*; + +pub const VERT_PAGE_SIZE: u32 = 256; + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Vertex { + pos: [f32; 3], + norm: [f32; 3], + col: [f32; 3], +} + +impl Vertex { + pub fn new(pos: Vec3, norm: Vec3, col: Vec3) -> Self { + Self { + pos: pos.into_array(), + norm: norm.into_array(), + col: col.into_array(), + } + } + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 3] = + wgpu::vertex_attr_array![0 => Float32x3, 1 => Float32x3, 2 => Float32x3]; + wgpu::VertexBufferLayout { + array_stride: Self::STRIDE, + step_mode: wgpu::InputStepMode::Vertex, + attributes: &ATTRIBUTES, + } + } +} + +// impl Default for Vertex { +// fn default() -> Self { Self::new(Vec2::zero(), Vec3::zero(), Vec3::zero()) } +// } + +impl VertexTrait for Vertex { + const QUADS_INDEX: Option = None;//Some(wgpu::IndexFormat::Uint16); + const STRIDE: wgpu::BufferAddress = mem::size_of::() as wgpu::BufferAddress; +} + +#[repr(C)] +#[derive(Copy, Clone, Debug, Zeroable, Pod)] +pub struct Instance { + inst_pos: [f32; 3], +} + +impl Instance { + pub fn new( + inst_pos: Vec3, + ) -> Self { + Self { + inst_pos: inst_pos.into_array(), + } + } + + fn desc<'a>() -> wgpu::VertexBufferLayout<'a> { + const ATTRIBUTES: [wgpu::VertexAttribute; 1] = wgpu::vertex_attr_array![ + 3 => Float32x3, + ]; + wgpu::VertexBufferLayout { + array_stride: mem::size_of::() as wgpu::BufferAddress, + step_mode: wgpu::InputStepMode::Instance, + attributes: &ATTRIBUTES, + } + } +} + +// impl Default for Instance { +// fn default() -> Self { Self::new(Mat4::identity(), 0.0, 0.0, Vec3::zero(), 0, 1.0, 0.0, 0) } +// } + +// TODO: ColLightsWrapper instead? +pub struct Locals; + +pub struct LodObjectPipeline { + pub pipeline: wgpu::RenderPipeline, +} + +impl LodObjectPipeline { + pub fn new( + device: &wgpu::Device, + vs_module: &wgpu::ShaderModule, + fs_module: &wgpu::ShaderModule, + global_layout: &GlobalsLayouts, + aa_mode: AaMode, + ) -> Self { + common_base::span!(_guard, "LodObjectPipeline::new"); + let render_pipeline_layout = + device.create_pipeline_layout(&wgpu::PipelineLayoutDescriptor { + label: Some("LoD object pipeline layout"), + push_constant_ranges: &[], + bind_group_layouts: &[ + &global_layout.globals, + &global_layout.shadow_textures, + ], + }); + + let samples = match aa_mode { + AaMode::None | AaMode::Fxaa => 1, + AaMode::MsaaX4 => 4, + AaMode::MsaaX8 => 8, + AaMode::MsaaX16 => 16, + }; + + let render_pipeline = device.create_render_pipeline(&wgpu::RenderPipelineDescriptor { + label: Some("LoD object pipeline"), + layout: Some(&render_pipeline_layout), + vertex: wgpu::VertexState { + module: vs_module, + entry_point: "main", + buffers: &[Vertex::desc(), Instance::desc()], + }, + primitive: wgpu::PrimitiveState { + topology: wgpu::PrimitiveTopology::TriangleList, + strip_index_format: None, + front_face: wgpu::FrontFace::Ccw, + cull_mode: Some(wgpu::Face::Back), + clamp_depth: false, + polygon_mode: wgpu::PolygonMode::Fill, + conservative: false, + }, + depth_stencil: Some(wgpu::DepthStencilState { + format: wgpu::TextureFormat::Depth32Float, + depth_write_enabled: true, + depth_compare: wgpu::CompareFunction::GreaterEqual, + stencil: wgpu::StencilState { + front: wgpu::StencilFaceState::IGNORE, + back: wgpu::StencilFaceState::IGNORE, + read_mask: !0, + write_mask: !0, + }, + bias: wgpu::DepthBiasState { + constant: 0, + slope_scale: 0.0, + clamp: 0.0, + }, + }), + multisample: wgpu::MultisampleState { + count: samples, + mask: !0, + alpha_to_coverage_enabled: false, + }, + fragment: Some(wgpu::FragmentState { + module: fs_module, + entry_point: "main", + targets: &[wgpu::ColorTargetState { + format: wgpu::TextureFormat::Rgba16Float, + blend: Some(wgpu::BlendState::REPLACE), + write_mask: wgpu::ColorWrite::ALL, + }], + }), + }); + + Self { + pipeline: render_pipeline, + } + } +} diff --git a/voxygen/src/render/pipelines/mod.rs b/voxygen/src/render/pipelines/mod.rs index f7476b6969..fe5f1f684d 100644 --- a/voxygen/src/render/pipelines/mod.rs +++ b/voxygen/src/render/pipelines/mod.rs @@ -4,6 +4,7 @@ pub mod clouds; pub mod debug; pub mod figure; pub mod fluid; +pub mod lod_object; pub mod lod_terrain; pub mod particle; pub mod postprocess; diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index 4f8a17baf2..f3d6096af3 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -21,7 +21,7 @@ use super::{ mesh::Mesh, model::{DynamicModel, Model}, pipelines::{ - blit, bloom, clouds, debug, figure, postprocess, shadow, sprite, terrain, ui, + blit, bloom, clouds, debug, figure, postprocess, shadow, sprite, lod_object, terrain, ui, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup, }, texture::Texture, diff --git a/voxygen/src/render/renderer/binding.rs b/voxygen/src/render/renderer/binding.rs index f42808c0a8..5e4074a58f 100644 --- a/voxygen/src/render/renderer/binding.rs +++ b/voxygen/src/render/renderer/binding.rs @@ -1,7 +1,7 @@ use super::{ super::{ pipelines::{ - debug, figure, lod_terrain, shadow, sprite, terrain, ui, ColLights, GlobalModel, + debug, figure, lod_terrain, shadow, sprite, lod_object, terrain, ui, ColLights, GlobalModel, GlobalsBindGroup, }, texture::Texture, diff --git a/voxygen/src/render/renderer/drawer.rs b/voxygen/src/render/renderer/drawer.rs index 1ce91101ca..1bf50519f5 100644 --- a/voxygen/src/render/renderer/drawer.rs +++ b/voxygen/src/render/renderer/drawer.rs @@ -5,7 +5,7 @@ use super::{ model::{DynamicModel, Model, SubModel}, pipelines::{ blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox, - sprite, terrain, trail, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup, + sprite, lod_object, terrain, trail, ui, ColLights, GlobalsBindGroup, ShadowTexturesBindGroup, }, }, Renderer, ShadowMap, ShadowMapRenderer, @@ -764,6 +764,17 @@ impl<'pass> FirstPassDrawer<'pass> { } } + pub fn draw_lod_objects<'data: 'pass>( + &mut self, + ) -> LodObjectDrawer<'_, 'pass> { + let mut render_pass = self.render_pass.scope("lod objects", self.borrow.device); + + render_pass.set_pipeline(&self.pipelines.lod_object.pipeline); + set_quad_index_buffer::(&mut render_pass, self.borrow); + + LodObjectDrawer { render_pass } + } + pub fn draw_fluid(&mut self) -> FluidDrawer<'_, 'pass> { let mut render_pass = self.render_pass.scope("fluid", self.borrow.device); @@ -909,6 +920,27 @@ impl<'pass_ref, 'pass: 'pass_ref> Drop for SpriteDrawer<'pass_ref, 'pass> { } } +#[must_use] +pub struct LodObjectDrawer<'pass_ref, 'pass: 'pass_ref> { + render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, +} + +impl<'pass_ref, 'pass: 'pass_ref> LodObjectDrawer<'pass_ref, 'pass> { + pub fn draw<'data: 'pass>( + &mut self, + model: &'data Model, + instances: &'data Instances, + ) { + self.render_pass.set_vertex_buffer(0, model.buf().slice(..)); + self.render_pass + .set_vertex_buffer(1, instances.buf().slice(..)); + self.render_pass.draw( + 0..model.len() as u32, + 0..instances.count() as u32, + ); + } +} + #[must_use] pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> { render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, diff --git a/voxygen/src/render/renderer/pipeline_creation.rs b/voxygen/src/render/renderer/pipeline_creation.rs index a4e8e34126..3311cab115 100644 --- a/voxygen/src/render/renderer/pipeline_creation.rs +++ b/voxygen/src/render/renderer/pipeline_creation.rs @@ -2,7 +2,7 @@ use super::{ super::{ pipelines::{ blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, - skybox, sprite, terrain, trail, ui, + skybox, sprite, lod_object, terrain, trail, ui, }, AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError, ShadowMode, @@ -28,6 +28,7 @@ pub struct Pipelines { // player_shadow: figure::FigurePipeline, pub skybox: skybox::SkyboxPipeline, pub sprite: sprite::SpritePipeline, + pub lod_object: lod_object::LodObjectPipeline, pub terrain: terrain::TerrainPipeline, pub ui: ui::UiPipeline, pub blit: blit::BlitPipeline, @@ -49,6 +50,7 @@ pub struct IngamePipelines { // player_shadow: figure::FigurePipeline, skybox: skybox::SkyboxPipeline, sprite: sprite::SpritePipeline, + lod_object: lod_object::LodObjectPipeline, terrain: terrain::TerrainPipeline, } @@ -85,6 +87,7 @@ impl Pipelines { //player_shadow: ingame.player_shadow, skybox: ingame.skybox, sprite: ingame.sprite, + lod_object: ingame.lod_object, terrain: ingame.terrain, ui: interface.ui, blit: interface.blit, @@ -106,6 +109,7 @@ struct ShaderModules { fluid_frag: wgpu::ShaderModule, sprite_vert: wgpu::ShaderModule, sprite_frag: wgpu::ShaderModule, + lod_object_vert: wgpu::ShaderModule, particle_vert: wgpu::ShaderModule, particle_frag: wgpu::ShaderModule, trail_vert: wgpu::ShaderModule, @@ -293,6 +297,7 @@ impl ShaderModules { fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?, sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?, sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?, + lod_object_vert: create_shader("lod-object-vert", ShaderKind::Vertex)?, particle_vert: create_shader("particle-vert", ShaderKind::Vertex)?, particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?, trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?, @@ -415,7 +420,7 @@ fn create_ingame_and_shadow_pipelines( needs: PipelineNeeds, pool: &rayon::ThreadPool, // TODO: Reduce the boilerplate in this file - tasks: [Task; 15], + tasks: [Task; 16], ) -> IngameAndShadowPipelines { prof_span!(_guard, "create_ingame_and_shadow_pipelines"); @@ -434,6 +439,7 @@ fn create_ingame_and_shadow_pipelines( terrain_task, fluid_task, sprite_task, + lod_object_task, particle_task, trail_task, lod_terrain_task, @@ -546,6 +552,21 @@ fn create_ingame_and_shadow_pipelines( "sprite pipeline creation", ) }; + // Pipeline for rendering lod objects + let create_lod_object = || { + lod_object_task.run( + || { + lod_object::LodObjectPipeline::new( + device, + &shaders.lod_object_vert, + &shaders.particle_frag, + &layouts.global, + pipeline_modes.aa, + ) + }, + "lod object pipeline creation", + ) + }; // Pipeline for rendering particles let create_particle = || { particle_task.run( @@ -732,6 +753,7 @@ fn create_ingame_and_shadow_pipelines( create_figure_directed_shadow, ) }; + let j7 = create_lod_object; // Ignore this let ( @@ -739,10 +761,10 @@ fn create_ingame_and_shadow_pipelines( ((debug, (skybox, figure)), (terrain, (fluid, bloom))), ((sprite, particle), (lod_terrain, (clouds, trail))), ), - ((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)), + (((postprocess, point_shadow), (terrain_directed_shadow, figure_directed_shadow)), lod_object), ) = pool.join( || pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)), - || pool.join(j5, j6), + || pool.join(|| pool.join(j5, j6), j7), ); IngameAndShadowPipelines { @@ -758,6 +780,7 @@ fn create_ingame_and_shadow_pipelines( postprocess, skybox, sprite, + lod_object, terrain, // player_shadow_pipeline, }, diff --git a/voxygen/src/render/renderer/shaders.rs b/voxygen/src/render/renderer/shaders.rs index 3a1bb00cfd..0863866175 100644 --- a/voxygen/src/render/renderer/shaders.rs +++ b/voxygen/src/render/renderer/shaders.rs @@ -61,6 +61,7 @@ impl assets::Compound for Shaders { "fluid-frag.shiny", "sprite-vert", "sprite-frag", + "lod-object-vert", "particle-vert", "particle-frag", "trail-vert", diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs index b796c14921..1b67de0e19 100644 --- a/voxygen/src/scene/lod.rs +++ b/voxygen/src/scene/lod.rs @@ -1,17 +1,27 @@ use crate::{ render::{ pipelines::lod_terrain::{LodData, Vertex}, - FirstPassDrawer, LodTerrainVertex, Mesh, Model, Quad, Renderer, + FirstPassDrawer, LodTerrainVertex, LodObjectVertex, Mesh, Model, Quad, Renderer, Instances, LodObjectInstance, Tri, }, + scene::GlobalModel, settings::Settings, }; +use hashbrown::HashMap; use client::Client; -use common::{spiral::Spiral2d, util::srgba_to_linear}; +use common::{ + assets::{ObjAsset, AssetExt}, + spiral::Spiral2d, + util::srgba_to_linear, + lod, +}; use vek::*; pub struct Lod { model: Option<(u32, Model)>, data: LodData, + + zone_objects: HashMap, HashMap>>, + object_data: HashMap>, } // TODO: Make constant when possible. @@ -21,19 +31,31 @@ pub fn water_color() -> Rgba { } impl Lod { - pub fn new(renderer: &mut Renderer, client: &Client, settings: &Settings) -> Self { + pub fn new( + renderer: &mut Renderer, + global_model: &GlobalModel, + client: &Client, + settings: &Settings, + ) -> Self { + let data = LodData::new( + renderer, + client.world_data().chunk_size().as_(), + client.world_data().lod_base.raw(), + client.world_data().lod_alt.raw(), + client.world_data().lod_horizon.raw(), + settings.graphics.lod_detail.max(100).min(2500), + /* TODO: figure out how we want to do this without color borders? + * water_color().into_array().into(), */ + ); Self { + zone_objects: HashMap::new(), + object_data: [ + (lod::ObjectKind::Tree, make_lod_object("tree", renderer, global_model, &data)), + ] + .into_iter() + .collect(), model: None, - data: LodData::new( - renderer, - client.world_data().chunk_size().as_(), - client.world_data().lod_base.raw(), - client.world_data().lod_alt.raw(), - client.world_data().lod_horizon.raw(), - settings.graphics.lod_detail.max(100).min(2500), - /* TODO: figure out how we want to do this without color borders? - * water_color().into_array().into(), */ - ), + data, } } @@ -44,7 +66,8 @@ impl Lod { self.data.tgt_detail = (detail - detail % 2).max(100).min(2500); } - pub fn maintain(&mut self, renderer: &mut Renderer) { + pub fn maintain(&mut self, renderer: &mut Renderer, client: &Client) { + // Update LoD terrain mesh according to detail if self .model .as_ref() @@ -58,12 +81,47 @@ impl Lod { .unwrap(), )); } + + // Maintain LoD object instances + for (p, zone) in client.lod_zones() { + self.zone_objects.entry(*p).or_insert_with(|| { + let mut objects = HashMap::<_, Vec<_>>::new(); + for object in zone.objects.iter() { + let pos = p.map(|e| lod::to_wpos(e) as f32).with_z(0.0) + + object.pos.map(|e| e as f32) + + Vec2::broadcast(0.5).with_z(0.0); + objects + .entry(object.kind) + .or_default() + .push(LodObjectInstance::new(pos)); + } + objects + .into_iter() + .map(|(kind, instances)| { + (kind, renderer.create_instances(&instances).expect("Renderer error?!")) + }) + .collect() + }); + } + + self.zone_objects.retain(|p, _| client.lod_zones().contains_key(p)); } pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) { if let Some((_, model)) = self.model.as_ref() { drawer.draw_lod_terrain(model); } + + // Draw LoD objects + for (kind, model) in &self.object_data { + let mut drawer = drawer.draw_lod_objects(); + for instances in self.zone_objects + .values() + .filter_map(|zone| zone.get(kind)) + { + drawer.draw(model, instances); + } + } } } @@ -94,3 +152,27 @@ fn create_lod_terrain_mesh(detail: u32) -> Mesh { }) .collect() } + +fn make_lod_object( + name: &str, + renderer: &mut Renderer, + global_model: &GlobalModel, + lod_data: &LodData, +) -> Model { + let model = ObjAsset::load_expect(&format!("voxygen.lod.{}", name)); + let mesh = model + .read().0 + .triangles() + .map(|vs| { + let [a, b, c] = vs.map(|v| LodObjectVertex::new( + v.position().into(), + v.normal().unwrap_or([0.0, 0.0, 1.0]).into(), + Vec3::broadcast(1.0), + )); + Tri::new(a, b, c) + }) + .collect(); + renderer + .create_model(&mesh) + .expect("Mesh was empty!") +} diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index 36b81468ae..3b32cf4f48 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -282,7 +282,7 @@ impl Scene { point_light_matrices: Box::new([PointLightMatrix::default(); MAX_LIGHT_COUNT * 6 + 6]), }; - let lod = Lod::new(renderer, client, settings); + let lod = Lod::new(renderer, &data, client, settings); let globals_bind_group = renderer.bind_globals(&data, lod.get_data()); @@ -681,7 +681,7 @@ impl Scene { renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv)); // Maintain LoD. - self.lod.maintain(renderer); + self.lod.maintain(renderer, client); // Maintain debug shapes self.debug.maintain(renderer); diff --git a/world/src/layer/tree.rs b/world/src/layer/tree.rs index 7d95a85602..d26c4e03e8 100644 --- a/world/src/layer/tree.rs +++ b/world/src/layer/tree.rs @@ -4,6 +4,7 @@ use crate::{ column::ColumnGen, util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser}, Canvas, + ColumnSample, }; use common::{ assets::AssetHandle, @@ -33,6 +34,23 @@ static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052); static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7); static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F); +// Ensure that it's valid to place a tree here +pub fn tree_valid_at(col: &ColumnSample, seed: u32) -> bool { + if col.alt < col.water_level + || col.spawn_rate < 0.9 + || col.water_dist.map(|d| d < 8.0).unwrap_or(false) + || col.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false) + { + return false + } + + if ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density { + return false; + } + + true +} + pub fn apply_trees_to( canvas: &mut Canvas, dynamic_rng: &mut impl Rng, @@ -68,17 +86,7 @@ pub fn apply_trees_to( let col = ColumnGen::new(info.chunks()).get((wpos, info.index(), calendar))?; - // Ensure that it's valid to place a *thing* here - if col.alt < col.water_level - || col.spawn_rate < 0.9 - || col.water_dist.map(|d| d < 8.0).unwrap_or(false) - || col.path.map(|(d, _, _, _)| d < 12.0).unwrap_or(false) - { - return None; - } - - // Ensure that it's valid to place a tree here - if ((seed.wrapping_mul(13)) & 0xFF) as f32 / 256.0 > col.tree_density { + if !tree_valid_at(&col, seed) { return None; } diff --git a/world/src/lib.rs b/world/src/lib.rs index f2a6297a4b..23598a46ce 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -56,10 +56,12 @@ use common::{ Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid, }, vol::{ReadVol, RectVolSize, WriteVol}, + lod, }; use common_net::msg::{world_msg, WorldMapMsg}; use rand::{prelude::*, Rng}; use rand_chacha::ChaCha8Rng; +use rayon::iter::ParallelIterator; use serde::Deserialize; use std::time::Duration; use vek::*; @@ -463,4 +465,32 @@ impl World { Ok((chunk, supplement)) } + + // Zone coordinates + pub fn get_lod_zone( + &self, + pos: Vec2, + index: IndexRef, + ) -> lod::Zone { + let min_wpos = pos.map(lod::to_wpos); + let max_wpos = (pos + 1).map(lod::to_wpos); + + let mut objects = Vec::new(); + + objects.append(&mut self.sim() + .get_area_trees(min_wpos, max_wpos) + .filter(|attr| { + ColumnGen::new(self.sim()).get((attr.pos, index, self.sim().calendar.as_ref())) + .map_or(false, |col| layer::tree::tree_valid_at(&col, attr.seed)) + }) + .map(|tree| lod::Object { + kind: lod::ObjectKind::Tree, + pos: (tree.pos - min_wpos) + .map(|e| e as u16) + .with_z(self.sim().get_alt_approx(tree.pos).unwrap_or(0.0) as u16), + }) + .collect()); + + lod::Zone { objects } + } } diff --git a/world/src/sim/mod.rs b/world/src/sim/mod.rs index f36d3a0640..592046c8e2 100644 --- a/world/src/sim/mod.rs +++ b/world/src/sim/mod.rs @@ -2134,36 +2134,36 @@ impl WorldSim { /// them spawning). pub fn get_near_trees(&self, wpos: Vec2) -> impl Iterator + '_ { // Deterministic based on wpos - let normal_trees = - self.gen_ctx - .structure_gen - .get(wpos) - .into_iter() - .filter_map(move |(wpos, seed)| { - let lottery = self.make_forest_lottery(wpos); - Some(TreeAttr { - pos: wpos, - seed, - scale: 1.0, - forest_kind: *lottery.choose_seeded(seed).as_ref()?, - inhabited: false, - }) - }); + self.gen_ctx + .structure_gen + .get(wpos) + .into_iter() + .filter_map(move |(wpos, seed)| { + let lottery = self.make_forest_lottery(wpos); + Some(TreeAttr { + pos: wpos, + seed, + scale: 1.0, + forest_kind: *lottery.choose_seeded(seed).as_ref()?, + inhabited: false, + }) + }) + } - // // For testing - // let giant_trees = - // std::array::IntoIter::new(self.gen_ctx.big_structure_gen.get(wpos)) - // // Don't even consider trees if we aren't close - // .filter(move |(pos, _)| pos.distance_squared(wpos) < 512i32.pow(2)) - // .map(move |(pos, seed)| TreeAttr { - // pos, - // seed, - // scale: 5.0, - // forest_kind: ForestKind::Giant, - // inhabited: (seed / 13) % 2 == 0, - // }); - - normal_trees //.chain(giant_trees) + pub fn get_area_trees(&self, wpos_min: Vec2, wpos_max: Vec2) -> impl ParallelIterator + '_ { + self.gen_ctx + .structure_gen + .par_iter(wpos_min, wpos_max) + .filter_map(move |(wpos, seed)| { + let lottery = self.make_forest_lottery(wpos); + Some(TreeAttr { + pos: wpos, + seed, + scale: 1.0, + forest_kind: *lottery.choose_seeded(seed).as_ref()?, + inhabited: false, + }) + }) } } diff --git a/world/src/util/structure.rs b/world/src/util/structure.rs index 2b7b8bfa1a..be3b6d1aee 100644 --- a/world/src/util/structure.rs +++ b/world/src/util/structure.rs @@ -102,19 +102,21 @@ impl StructureGen2d { let x_field = self.x_field; let y_field = self.y_field; let seed_field = self.seed_field; - (0..len).into_par_iter().map(move |xy| { - let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32); - Self::index_to_sample_internal( - freq, - freq_offset, - spread, - spread_mul, - x_field, - y_field, - seed_field, - index, - ) - }) + (0..len) + .into_par_iter() + .map(move |xy| { + let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32); + Self::index_to_sample_internal( + freq, + freq_offset, + spread, + spread_mul, + x_field, + y_field, + seed_field, + index, + ) + }) } }