Initial implementation of LoD trees

This commit is contained in:
Joshua Barretto 2022-05-09 00:53:19 +01:00
parent 3a984d24a5
commit b3126ca687
30 changed files with 906 additions and 79 deletions

10
Cargo.lock generated
View File

@ -6560,6 +6560,7 @@ dependencies = [
"serde", "serde",
"tracing", "tracing",
"walkdir 2.3.2", "walkdir 2.3.2",
"wavefront",
] ]
[[package]] [[package]]
@ -7340,6 +7341,15 @@ dependencies = [
"wast", "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]] [[package]]
name = "wayland-client" name = "wayland-client"
version = "0.28.6" version = "0.28.6"

View File

@ -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

244
assets/voxygen/lod/tree.obj Normal file
View File

@ -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

View File

@ -0,0 +1,43 @@
#version 420 core
#include <constants.glsl>
#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 <globals.glsl>
#include <srgb.glsl>
#include <random.glsl>
#include <lod.glsl>
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);
}

View File

@ -46,6 +46,8 @@ use common::{
trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult}, trade::{PendingTrade, SitePrices, TradeAction, TradeId, TradeResult},
uid::{Uid, UidAllocator}, uid::{Uid, UidAllocator},
vol::RectVolSize, vol::RectVolSize,
spiral::Spiral2d,
lod,
}; };
use common_base::{prof_span, span}; use common_base::{prof_span, span};
use common_net::{ use common_net::{
@ -171,10 +173,12 @@ pub struct Client {
pub chat_mode: ChatMode, pub chat_mode: ChatMode,
recipe_book: RecipeBook, recipe_book: RecipeBook,
available_recipes: HashMap<String, Option<SpriteKind>>, available_recipes: HashMap<String, Option<SpriteKind>>,
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
lod_last_requested: Option<Instant>,
max_group_size: u32, max_group_size: u32,
// Client has received an invite (inviter uid, time out instant) // 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<Uid>, group_leader: Option<Uid>,
// Note: potentially representable as a client only component // Note: potentially representable as a client only component
group_members: HashMap<Uid, group::Role>, group_members: HashMap<Uid, group::Role>,
@ -202,6 +206,7 @@ pub struct Client {
state: State, state: State,
view_distance: Option<u32>, view_distance: Option<u32>,
lod_distance: u32,
// TODO: move into voxygen // TODO: move into voxygen
loaded_distance: f32, loaded_distance: f32,
@ -624,6 +629,9 @@ impl Client {
available_recipes: HashMap::default(), available_recipes: HashMap::default(),
chat_mode: ChatMode::default(), chat_mode: ChatMode::default(),
lod_zones: HashMap::new(),
lod_last_requested: None,
max_group_size, max_group_size,
invite: None, invite: None,
group_leader: None, group_leader: None,
@ -650,6 +658,7 @@ impl Client {
tick: 0, tick: 0,
state, state,
view_distance: None, view_distance: None,
lod_distance: 2, // TODO: Make configurable
loaded_distance: 0.0, loaded_distance: 0.0,
pending_chunks: HashMap::new(), pending_chunks: HashMap::new(),
@ -769,7 +778,8 @@ impl Client {
&mut self.in_game_stream &mut self.in_game_stream
}, },
//Only in game, terrain //Only in game, terrain
ClientGeneral::TerrainChunkRequest { .. } => { ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. } => {
#[cfg(feature = "tracy")] #[cfg(feature = "tracy")]
{ {
terrain = 1.0; terrain = 1.0;
@ -994,6 +1004,10 @@ impl Client {
&self.available_recipes &self.available_recipes
} }
pub fn lod_zones(&self) -> &HashMap<Vec2<i32>, lod::Zone> {
&self.lod_zones
}
/// Returns whether the specified recipe can be crafted and the sprite, if /// Returns whether the specified recipe can be crafted and the sprite, if
/// any, that is required to do so. /// any, that is required to do so.
pub fn can_craft_recipe(&self, recipe: &str) -> (bool, Option<SpriteKind>) { pub fn can_craft_recipe(&self, recipe: &str) -> (bool, Option<SpriteKind>) {
@ -1709,6 +1723,27 @@ impl Client {
let now = Instant::now(); let now = Instant::now();
self.pending_chunks self.pending_chunks
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(3)); .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(()) Ok(())
@ -2084,6 +2119,10 @@ impl Client {
} }
self.pending_chunks.remove(&key); self.pending_chunks.remove(&key);
}, },
ServerGeneral::LodZoneUpdate { key, zone } => {
self.lod_zones.insert(key, zone);
self.lod_last_requested = None;
},
ServerGeneral::TerrainBlockUpdates(blocks) => { ServerGeneral::TerrainBlockUpdates(blocks) => {
if let Some(mut blocks) = blocks.decompress() { if let Some(mut blocks) = blocks.decompress() {
blocks.drain().for_each(|(pos, block)| { blocks.drain().for_each(|(pos, block)| {

View File

@ -10,6 +10,7 @@ lazy_static = "1.4.0"
assets_manager = {version = "0.7", features = ["bincode", "ron", "json"]} assets_manager = {version = "0.7", features = ["bincode", "ron", "json"]}
ron = { version = "0.7", default-features = false } ron = { version = "0.7", default-features = false }
dot_vox = "4.0" dot_vox = "4.0"
wavefront = "0.2"
image = { version = "0.23.12", default-features = false, features = ["png"] } image = { version = "0.23.12", default-features = false, features = ["png"] }
tracing = "0.1" tracing = "0.1"

View File

@ -191,6 +191,22 @@ impl Asset for DotVoxAsset {
const EXTENSION: &'static str = "vox"; 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<ObjAsset> for ObjAssetLoader {
fn load(content: std::borrow::Cow<[u8]>, _: &str) -> Result<ObjAsset, BoxedError> {
let data = wavefront::Obj::from_reader(&*content)?;
Ok(ObjAsset(data))
}
}
/// Return path to repository root by searching 10 directories back /// Return path to repository root by searching 10 directories back
pub fn find_root() -> Option<PathBuf> { pub fn find_root() -> Option<PathBuf> {
std::env::current_dir().map_or(None, |path| { std::env::current_dir().map_or(None, |path| {

View File

@ -83,6 +83,9 @@ pub enum ClientGeneral {
TerrainChunkRequest { TerrainChunkRequest {
key: Vec2<i32>, key: Vec2<i32>,
}, },
LodZoneRequest {
key: Vec2<i32>,
},
//Always possible //Always possible
ChatMsg(String), ChatMsg(String),
Command(String, Vec<String>), Command(String, Vec<String>),
@ -128,6 +131,7 @@ impl ClientMsg {
| ClientGeneral::ExitInGame | ClientGeneral::ExitInGame
| ClientGeneral::PlayerPhysics { .. } | ClientGeneral::PlayerPhysics { .. }
| ClientGeneral::TerrainChunkRequest { .. } | ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. }
| ClientGeneral::UnlockSkill(_) | ClientGeneral::UnlockSkill(_)
| ClientGeneral::RequestSiteInfo(_) | ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::UnlockSkillGroup(_) | ClientGeneral::UnlockSkillGroup(_)

View File

@ -13,6 +13,7 @@ use common::{
terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize}, terrain::{Block, TerrainChunk, TerrainChunkMeta, TerrainChunkSize},
trade::{PendingTrade, SitePrices, TradeId, TradeResult}, trade::{PendingTrade, SitePrices, TradeId, TradeResult},
uid::Uid, uid::Uid,
lod,
}; };
use hashbrown::HashMap; use hashbrown::HashMap;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
@ -169,6 +170,10 @@ pub enum ServerGeneral {
key: Vec2<i32>, key: Vec2<i32>,
chunk: Result<SerializedTerrainChunk, ()>, chunk: Result<SerializedTerrainChunk, ()>,
}, },
LodZoneUpdate {
key: Vec2<i32>,
zone: lod::Zone,
},
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>), TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, Block>>),
// Always possible // Always possible
PlayerListUpdate(PlayerListUpdate), PlayerListUpdate(PlayerListUpdate),
@ -293,6 +298,7 @@ impl ServerMsg {
| ServerGeneral::ExitInGameSuccess | ServerGeneral::ExitInGameSuccess
| ServerGeneral::InventoryUpdate(_, _) | ServerGeneral::InventoryUpdate(_, _)
| ServerGeneral::TerrainChunkUpdate { .. } | ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::LodZoneUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_) | ServerGeneral::TerrainBlockUpdates(_)
| ServerGeneral::SetViewDistance(_) | ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_) | ServerGeneral::Outcomes(_)

View File

@ -49,6 +49,7 @@ pub mod figure;
pub mod generation; pub mod generation;
#[cfg(not(target_arch = "wasm32"))] pub mod grid; #[cfg(not(target_arch = "wasm32"))] pub mod grid;
#[cfg(not(target_arch = "wasm32"))] pub mod link; #[cfg(not(target_arch = "wasm32"))] pub mod link;
#[cfg(not(target_arch = "wasm32"))] pub mod lod;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]
pub mod lottery; pub mod lottery;
#[cfg(not(target_arch = "wasm32"))] #[cfg(not(target_arch = "wasm32"))]

35
common/src/lod.rs Normal file
View File

@ -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<u16>,
}
#[derive(Clone, Debug, Serialize, Deserialize)]
pub struct Zone {
pub objects: Vec<Object>,
}
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
}

View File

@ -118,6 +118,7 @@ impl Client {
}, },
//Ingame related, terrain //Ingame related, terrain
ServerGeneral::TerrainChunkUpdate { .. } ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::LodZoneUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_) => { | ServerGeneral::TerrainBlockUpdates(_) => {
self.terrain_stream.lock().unwrap().send(g) self.terrain_stream.lock().unwrap().send(g)
}, },
@ -191,6 +192,7 @@ impl Client {
}, },
//Ingame related, terrain //Ingame related, terrain
ServerGeneral::TerrainChunkUpdate { .. } ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::LodZoneUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_) => { | ServerGeneral::TerrainBlockUpdates(_) => {
PreparedMsg::new(5, &g, &self.terrain_stream_params) PreparedMsg::new(5, &g, &self.terrain_stream_params)
}, },

View File

@ -24,6 +24,7 @@ pub mod error;
pub mod events; pub mod events;
pub mod input; pub mod input;
pub mod location; pub mod location;
pub mod lod;
pub mod login_provider; pub mod login_provider;
pub mod metrics; pub mod metrics;
pub mod persistence; pub mod persistence;
@ -449,6 +450,7 @@ impl Server {
// Insert the world into the ECS (todo: Maybe not an Arc?) // Insert the world into the ECS (todo: Maybe not an Arc?)
let world = Arc::new(world); let world = Arc::new(world);
state.ecs_mut().insert(Arc::clone(&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()); state.ecs_mut().insert(index.clone());
// Set starting time for the server. // Set starting time for the server.

33
server/src/lod.rs Normal file
View File

@ -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<Vec2<i32>, 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<i32>) -> &lod::Zone {
self.zones.get(&zone_pos).unwrap_or(&EMPTY_ZONE)
}
}

View File

@ -295,6 +295,7 @@ impl Sys {
| ClientGeneral::Character(_) | ClientGeneral::Character(_)
| ClientGeneral::Spectate | ClientGeneral::Spectate
| ClientGeneral::TerrainChunkRequest { .. } | ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. }
| ClientGeneral::ChatMsg(_) | ClientGeneral::ChatMsg(_)
| ClientGeneral::Command(..) | ClientGeneral::Command(..)
| ClientGeneral::Terminate => { | ClientGeneral::Terminate => {

View File

@ -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::{ use common::{
comp::Pos, comp::Pos,
event::{EventBus, ServerEvent}, event::{EventBus, ServerEvent},
@ -20,6 +20,7 @@ impl<'a> System<'a> for Sys {
Entities<'a>, Entities<'a>,
Read<'a, EventBus<ServerEvent>>, Read<'a, EventBus<ServerEvent>>,
ReadExpect<'a, TerrainGrid>, ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, Lod>,
ReadExpect<'a, NetworkRequestMetrics>, ReadExpect<'a, NetworkRequestMetrics>,
Write<'a, Vec<ChunkRequest>>, Write<'a, Vec<ChunkRequest>>,
ReadStorage<'a, Pos>, ReadStorage<'a, Pos>,
@ -37,6 +38,7 @@ impl<'a> System<'a> for Sys {
entities, entities,
server_event_bus, server_event_bus,
terrain, terrain,
lod,
network_metrics, network_metrics,
mut chunk_requests, mut chunk_requests,
positions, positions,
@ -101,6 +103,12 @@ impl<'a> System<'a> for Sys {
network_metrics.chunks_request_dropped.inc(); network_metrics.chunks_request_dropped.inc();
} }
}, },
ClientGeneral::LodZoneRequest { key } => {
client.send(ServerGeneral::LodZoneUpdate {
key,
zone: lod.zone(key).clone(),
})?;
},
_ => { _ => {
debug!( debug!(
"Kicking possibly misbehaving client due to invalud terrain \ "Kicking possibly misbehaving client due to invalud terrain \

View File

@ -35,6 +35,10 @@ pub use self::{
Instance as SpriteInstance, SpriteGlobalsBindGroup, SpriteVerts, Instance as SpriteInstance, SpriteGlobalsBindGroup, SpriteVerts,
Vertex as SpriteVertex, VERT_PAGE_SIZE as SPRITE_VERT_PAGE_SIZE, 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}, terrain::{Locals as TerrainLocals, TerrainLayout, Vertex as TerrainVertex},
trail::Vertex as TrailVertex, trail::Vertex as TrailVertex,
ui::{ ui::{

View File

@ -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<f32>, norm: Vec3<f32>, col: Vec3<f32>) -> 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<wgpu::IndexFormat> = None;//Some(wgpu::IndexFormat::Uint16);
const STRIDE: wgpu::BufferAddress = mem::size_of::<Self>() 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<f32>,
) -> 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::<Self>() 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,
}
}
}

View File

@ -4,6 +4,7 @@ pub mod clouds;
pub mod debug; pub mod debug;
pub mod figure; pub mod figure;
pub mod fluid; pub mod fluid;
pub mod lod_object;
pub mod lod_terrain; pub mod lod_terrain;
pub mod particle; pub mod particle;
pub mod postprocess; pub mod postprocess;

View File

@ -21,7 +21,7 @@ use super::{
mesh::Mesh, mesh::Mesh,
model::{DynamicModel, Model}, model::{DynamicModel, Model},
pipelines::{ 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, GlobalsBindGroup, GlobalsLayouts, ShadowTexturesBindGroup,
}, },
texture::Texture, texture::Texture,

View File

@ -1,7 +1,7 @@
use super::{ use super::{
super::{ super::{
pipelines::{ pipelines::{
debug, figure, lod_terrain, shadow, sprite, terrain, ui, ColLights, GlobalModel, debug, figure, lod_terrain, shadow, sprite, lod_object, terrain, ui, ColLights, GlobalModel,
GlobalsBindGroup, GlobalsBindGroup,
}, },
texture::Texture, texture::Texture,

View File

@ -5,7 +5,7 @@ use super::{
model::{DynamicModel, Model, SubModel}, model::{DynamicModel, Model, SubModel},
pipelines::{ pipelines::{
blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, shadow, skybox, 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, 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::<lod_object::Vertex>(&mut render_pass, self.borrow);
LodObjectDrawer { render_pass }
}
pub fn draw_fluid(&mut self) -> FluidDrawer<'_, 'pass> { pub fn draw_fluid(&mut self) -> FluidDrawer<'_, 'pass> {
let mut render_pass = self.render_pass.scope("fluid", self.borrow.device); 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<lod_object::Vertex>,
instances: &'data Instances<lod_object::Instance>,
) {
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] #[must_use]
pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> { pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> {
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>, render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,

View File

@ -2,7 +2,7 @@ use super::{
super::{ super::{
pipelines::{ pipelines::{
blit, bloom, clouds, debug, figure, fluid, lod_terrain, particle, postprocess, shadow, 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, AaMode, BloomMode, CloudMode, FluidMode, LightingMode, PipelineModes, RenderError,
ShadowMode, ShadowMode,
@ -28,6 +28,7 @@ pub struct Pipelines {
// player_shadow: figure::FigurePipeline, // player_shadow: figure::FigurePipeline,
pub skybox: skybox::SkyboxPipeline, pub skybox: skybox::SkyboxPipeline,
pub sprite: sprite::SpritePipeline, pub sprite: sprite::SpritePipeline,
pub lod_object: lod_object::LodObjectPipeline,
pub terrain: terrain::TerrainPipeline, pub terrain: terrain::TerrainPipeline,
pub ui: ui::UiPipeline, pub ui: ui::UiPipeline,
pub blit: blit::BlitPipeline, pub blit: blit::BlitPipeline,
@ -49,6 +50,7 @@ pub struct IngamePipelines {
// player_shadow: figure::FigurePipeline, // player_shadow: figure::FigurePipeline,
skybox: skybox::SkyboxPipeline, skybox: skybox::SkyboxPipeline,
sprite: sprite::SpritePipeline, sprite: sprite::SpritePipeline,
lod_object: lod_object::LodObjectPipeline,
terrain: terrain::TerrainPipeline, terrain: terrain::TerrainPipeline,
} }
@ -85,6 +87,7 @@ impl Pipelines {
//player_shadow: ingame.player_shadow, //player_shadow: ingame.player_shadow,
skybox: ingame.skybox, skybox: ingame.skybox,
sprite: ingame.sprite, sprite: ingame.sprite,
lod_object: ingame.lod_object,
terrain: ingame.terrain, terrain: ingame.terrain,
ui: interface.ui, ui: interface.ui,
blit: interface.blit, blit: interface.blit,
@ -106,6 +109,7 @@ struct ShaderModules {
fluid_frag: wgpu::ShaderModule, fluid_frag: wgpu::ShaderModule,
sprite_vert: wgpu::ShaderModule, sprite_vert: wgpu::ShaderModule,
sprite_frag: wgpu::ShaderModule, sprite_frag: wgpu::ShaderModule,
lod_object_vert: wgpu::ShaderModule,
particle_vert: wgpu::ShaderModule, particle_vert: wgpu::ShaderModule,
particle_frag: wgpu::ShaderModule, particle_frag: wgpu::ShaderModule,
trail_vert: wgpu::ShaderModule, trail_vert: wgpu::ShaderModule,
@ -293,6 +297,7 @@ impl ShaderModules {
fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?, fluid_frag: create_shader(&selected_fluid_shader, ShaderKind::Fragment)?,
sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?, sprite_vert: create_shader("sprite-vert", ShaderKind::Vertex)?,
sprite_frag: create_shader("sprite-frag", ShaderKind::Fragment)?, 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_vert: create_shader("particle-vert", ShaderKind::Vertex)?,
particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?, particle_frag: create_shader("particle-frag", ShaderKind::Fragment)?,
trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?, trail_vert: create_shader("trail-vert", ShaderKind::Vertex)?,
@ -415,7 +420,7 @@ fn create_ingame_and_shadow_pipelines(
needs: PipelineNeeds, needs: PipelineNeeds,
pool: &rayon::ThreadPool, pool: &rayon::ThreadPool,
// TODO: Reduce the boilerplate in this file // TODO: Reduce the boilerplate in this file
tasks: [Task; 15], tasks: [Task; 16],
) -> IngameAndShadowPipelines { ) -> IngameAndShadowPipelines {
prof_span!(_guard, "create_ingame_and_shadow_pipelines"); prof_span!(_guard, "create_ingame_and_shadow_pipelines");
@ -434,6 +439,7 @@ fn create_ingame_and_shadow_pipelines(
terrain_task, terrain_task,
fluid_task, fluid_task,
sprite_task, sprite_task,
lod_object_task,
particle_task, particle_task,
trail_task, trail_task,
lod_terrain_task, lod_terrain_task,
@ -546,6 +552,21 @@ fn create_ingame_and_shadow_pipelines(
"sprite pipeline creation", "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 // Pipeline for rendering particles
let create_particle = || { let create_particle = || {
particle_task.run( particle_task.run(
@ -732,6 +753,7 @@ fn create_ingame_and_shadow_pipelines(
create_figure_directed_shadow, create_figure_directed_shadow,
) )
}; };
let j7 = create_lod_object;
// Ignore this // Ignore this
let ( let (
@ -739,10 +761,10 @@ fn create_ingame_and_shadow_pipelines(
((debug, (skybox, figure)), (terrain, (fluid, bloom))), ((debug, (skybox, figure)), (terrain, (fluid, bloom))),
((sprite, particle), (lod_terrain, (clouds, trail))), ((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(|| pool.join(j1, j2), || pool.join(j3, j4)), || pool.join(|| pool.join(j1, j2), || pool.join(j3, j4)),
|| pool.join(j5, j6), || pool.join(|| pool.join(j5, j6), j7),
); );
IngameAndShadowPipelines { IngameAndShadowPipelines {
@ -758,6 +780,7 @@ fn create_ingame_and_shadow_pipelines(
postprocess, postprocess,
skybox, skybox,
sprite, sprite,
lod_object,
terrain, terrain,
// player_shadow_pipeline, // player_shadow_pipeline,
}, },

View File

@ -61,6 +61,7 @@ impl assets::Compound for Shaders {
"fluid-frag.shiny", "fluid-frag.shiny",
"sprite-vert", "sprite-vert",
"sprite-frag", "sprite-frag",
"lod-object-vert",
"particle-vert", "particle-vert",
"particle-frag", "particle-frag",
"trail-vert", "trail-vert",

View File

@ -1,17 +1,27 @@
use crate::{ use crate::{
render::{ render::{
pipelines::lod_terrain::{LodData, Vertex}, 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, settings::Settings,
}; };
use hashbrown::HashMap;
use client::Client; 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::*; use vek::*;
pub struct Lod { pub struct Lod {
model: Option<(u32, Model<LodTerrainVertex>)>, model: Option<(u32, Model<LodTerrainVertex>)>,
data: LodData, data: LodData,
zone_objects: HashMap<Vec2<i32>, HashMap<lod::ObjectKind, Instances<LodObjectInstance>>>,
object_data: HashMap<lod::ObjectKind, Model<LodObjectVertex>>,
} }
// TODO: Make constant when possible. // TODO: Make constant when possible.
@ -21,19 +31,31 @@ pub fn water_color() -> Rgba<f32> {
} }
impl Lod { 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 { Self {
zone_objects: HashMap::new(),
object_data: [
(lod::ObjectKind::Tree, make_lod_object("tree", renderer, global_model, &data)),
]
.into_iter()
.collect(),
model: None, model: None,
data: LodData::new( data,
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(), */
),
} }
} }
@ -44,7 +66,8 @@ impl Lod {
self.data.tgt_detail = (detail - detail % 2).max(100).min(2500); 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 if self
.model .model
.as_ref() .as_ref()
@ -58,12 +81,47 @@ impl Lod {
.unwrap(), .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>) { pub fn render<'a>(&'a self, drawer: &mut FirstPassDrawer<'a>) {
if let Some((_, model)) = self.model.as_ref() { if let Some((_, model)) = self.model.as_ref() {
drawer.draw_lod_terrain(model); 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<LodTerrainVertex> {
}) })
.collect() .collect()
} }
fn make_lod_object(
name: &str,
renderer: &mut Renderer,
global_model: &GlobalModel,
lod_data: &LodData,
) -> Model<LodObjectVertex> {
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!")
}

View File

@ -282,7 +282,7 @@ impl Scene {
point_light_matrices: Box::new([PointLightMatrix::default(); MAX_LIGHT_COUNT * 6 + 6]), 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()); 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)); renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
// Maintain LoD. // Maintain LoD.
self.lod.maintain(renderer); self.lod.maintain(renderer, client);
// Maintain debug shapes // Maintain debug shapes
self.debug.maintain(renderer); self.debug.maintain(renderer);

View File

@ -4,6 +4,7 @@ use crate::{
column::ColumnGen, column::ColumnGen,
util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser}, util::{gen_cache::StructureGenCache, RandomPerm, Sampler, UnitChooser},
Canvas, Canvas,
ColumnSample,
}; };
use common::{ use common::{
assets::AssetHandle, assets::AssetHandle,
@ -33,6 +34,23 @@ static MODEL_RAND: RandomPerm = RandomPerm::new(0xDB21C052);
static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7); static UNIT_CHOOSER: UnitChooser = UnitChooser::new(0x700F4EC7);
static QUIRKY_RAND: RandomPerm = RandomPerm::new(0xA634460F); 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( pub fn apply_trees_to(
canvas: &mut Canvas, canvas: &mut Canvas,
dynamic_rng: &mut impl Rng, 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))?; let col = ColumnGen::new(info.chunks()).get((wpos, info.index(), calendar))?;
// Ensure that it's valid to place a *thing* here if !tree_valid_at(&col, seed) {
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 {
return None; return None;
} }

View File

@ -56,10 +56,12 @@ use common::{
Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid, Block, BlockKind, SpriteKind, TerrainChunk, TerrainChunkMeta, TerrainChunkSize, TerrainGrid,
}, },
vol::{ReadVol, RectVolSize, WriteVol}, vol::{ReadVol, RectVolSize, WriteVol},
lod,
}; };
use common_net::msg::{world_msg, WorldMapMsg}; use common_net::msg::{world_msg, WorldMapMsg};
use rand::{prelude::*, Rng}; use rand::{prelude::*, Rng};
use rand_chacha::ChaCha8Rng; use rand_chacha::ChaCha8Rng;
use rayon::iter::ParallelIterator;
use serde::Deserialize; use serde::Deserialize;
use std::time::Duration; use std::time::Duration;
use vek::*; use vek::*;
@ -463,4 +465,32 @@ impl World {
Ok((chunk, supplement)) Ok((chunk, supplement))
} }
// Zone coordinates
pub fn get_lod_zone(
&self,
pos: Vec2<i32>,
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 }
}
} }

View File

@ -2134,36 +2134,36 @@ impl WorldSim {
/// them spawning). /// them spawning).
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ { pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
// Deterministic based on wpos // Deterministic based on wpos
let normal_trees = self.gen_ctx
self.gen_ctx .structure_gen
.structure_gen .get(wpos)
.get(wpos) .into_iter()
.into_iter() .filter_map(move |(wpos, seed)| {
.filter_map(move |(wpos, seed)| { let lottery = self.make_forest_lottery(wpos);
let lottery = self.make_forest_lottery(wpos); Some(TreeAttr {
Some(TreeAttr { pos: wpos,
pos: wpos, seed,
seed, scale: 1.0,
scale: 1.0, forest_kind: *lottery.choose_seeded(seed).as_ref()?,
forest_kind: *lottery.choose_seeded(seed).as_ref()?, inhabited: false,
inhabited: false, })
}) })
}); }
// // For testing pub fn get_area_trees(&self, wpos_min: Vec2<i32>, wpos_max: Vec2<i32>) -> impl ParallelIterator<Item = TreeAttr> + '_ {
// let giant_trees = self.gen_ctx
// std::array::IntoIter::new(self.gen_ctx.big_structure_gen.get(wpos)) .structure_gen
// // Don't even consider trees if we aren't close .par_iter(wpos_min, wpos_max)
// .filter(move |(pos, _)| pos.distance_squared(wpos) < 512i32.pow(2)) .filter_map(move |(wpos, seed)| {
// .map(move |(pos, seed)| TreeAttr { let lottery = self.make_forest_lottery(wpos);
// pos, Some(TreeAttr {
// seed, pos: wpos,
// scale: 5.0, seed,
// forest_kind: ForestKind::Giant, scale: 1.0,
// inhabited: (seed / 13) % 2 == 0, forest_kind: *lottery.choose_seeded(seed).as_ref()?,
// }); inhabited: false,
})
normal_trees //.chain(giant_trees) })
} }
} }

View File

@ -102,19 +102,21 @@ impl StructureGen2d {
let x_field = self.x_field; let x_field = self.x_field;
let y_field = self.y_field; let y_field = self.y_field;
let seed_field = self.seed_field; let seed_field = self.seed_field;
(0..len).into_par_iter().map(move |xy| { (0..len)
let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32); .into_par_iter()
Self::index_to_sample_internal( .map(move |xy| {
freq, let index = min_index + Vec2::new((xy % xlen as u64) as i32, (xy / xlen as u64) as i32);
freq_offset, Self::index_to_sample_internal(
spread, freq,
spread_mul, freq_offset,
x_field, spread,
y_field, spread_mul,
seed_field, x_field,
index, y_field,
) seed_field,
}) index,
)
})
} }
} }