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",
"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"

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},
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<String, Option<SpriteKind>>,
lod_zones: HashMap<Vec2<i32>, lod::Zone>,
lod_last_requested: Option<Instant>,
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<Uid>,
// Note: potentially representable as a client only component
group_members: HashMap<Uid, group::Role>,
@ -202,6 +206,7 @@ pub struct Client {
state: State,
view_distance: Option<u32>,
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<Vec2<i32>, 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<SpriteKind>) {
@ -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)| {

View File

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

View File

@ -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<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
pub fn find_root() -> Option<PathBuf> {
std::env::current_dir().map_or(None, |path| {

View File

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

View File

@ -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<i32>,
chunk: Result<SerializedTerrainChunk, ()>,
},
LodZoneUpdate {
key: Vec2<i32>,
zone: lod::Zone,
},
TerrainBlockUpdates(CompressedData<HashMap<Vec3<i32>, 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(_)

View File

@ -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"))]

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
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)
},

View File

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

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::Spectate
| ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. }
| ClientGeneral::ChatMsg(_)
| ClientGeneral::Command(..)
| 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::{
comp::Pos,
event::{EventBus, ServerEvent},
@ -20,6 +20,7 @@ impl<'a> System<'a> for Sys {
Entities<'a>,
Read<'a, EventBus<ServerEvent>>,
ReadExpect<'a, TerrainGrid>,
ReadExpect<'a, Lod>,
ReadExpect<'a, NetworkRequestMetrics>,
Write<'a, Vec<ChunkRequest>>,
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 \

View File

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

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 figure;
pub mod fluid;
pub mod lod_object;
pub mod lod_terrain;
pub mod particle;
pub mod postprocess;

View File

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

View File

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

View File

@ -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::<lod_object::Vertex>(&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<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]
pub struct FluidDrawer<'pass_ref, 'pass: 'pass_ref> {
render_pass: Scope<'pass_ref, wgpu::RenderPass<'pass>>,

View File

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

View File

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

View File

@ -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<LodTerrainVertex>)>,
data: LodData,
zone_objects: HashMap<Vec2<i32>, HashMap<lod::ObjectKind, Instances<LodObjectInstance>>>,
object_data: HashMap<lod::ObjectKind, Model<LodObjectVertex>>,
}
// TODO: Make constant when possible.
@ -21,19 +31,31 @@ pub fn water_color() -> Rgba<f32> {
}
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<LodTerrainVertex> {
})
.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]),
};
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);

View File

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

View File

@ -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<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).
pub fn get_near_trees(&self, wpos: Vec2<i32>) -> impl Iterator<Item = TreeAttr> + '_ {
// 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<i32>, wpos_max: Vec2<i32>) -> impl ParallelIterator<Item = TreeAttr> + '_ {
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,
})
})
}
}

View File

@ -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,
)
})
}
}