From 10245e0c1b0cb6fae10d86409435364edc6102ef Mon Sep 17 00:00:00 2001 From: Joshua Yanovski Date: Sat, 15 Aug 2020 21:15:02 +0200 Subject: [PATCH] Merge more models into one mesh than we did previously. --- common/src/comp/agent.rs | 19 +- common/src/comp/body.rs | 7 +- common/src/comp/character_state.rs | 35 ++-- common/src/figure/mat_cell.rs | 7 +- common/src/lib.rs | 1 - common/src/terrain/block.rs | 7 +- common/src/terrain/map.rs | 6 +- common/src/terrain/structure.rs | 7 +- network/src/lib.rs | 2 +- network/src/message.rs | 46 +---- rust-toolchain | 2 +- server/src/client.rs | 16 +- server/src/events/entity_manipulation.rs | 9 + server/src/events/inventory_manip.rs | 3 + server/src/state_ext.rs | 1 - server/src/sys/entity_sync.rs | 2 +- server/src/sys/message.rs | 2 +- voxygen/src/audio/mod.rs | 9 +- .../src/audio/sfx/event_mapper/combat/mod.rs | 6 +- voxygen/src/hud/chat.rs | 5 +- voxygen/src/hud/mod.rs | 14 +- voxygen/src/lib.rs | 4 +- voxygen/src/mesh/greedy.rs | 53 +++--- voxygen/src/mesh/segment.rs | 125 ++++++++----- voxygen/src/mesh/terrain.rs | 37 ++-- voxygen/src/render/consts.rs | 6 +- voxygen/src/render/instances.rs | 2 +- voxygen/src/render/mesh.rs | 7 +- voxygen/src/render/mod.rs | 2 +- voxygen/src/render/model.rs | 13 +- voxygen/src/render/pipelines/figure.rs | 18 +- voxygen/src/render/pipelines/shadow.rs | 56 +----- voxygen/src/render/pipelines/sprite.rs | 10 +- voxygen/src/render/pipelines/terrain.rs | 18 +- voxygen/src/render/renderer.rs | 23 +-- voxygen/src/scene/figure/cache.rs | 137 ++++++++++---- voxygen/src/scene/figure/load.rs | 2 +- voxygen/src/scene/figure/mod.rs | 173 +++++++++++------- voxygen/src/scene/lod.rs | 6 +- voxygen/src/scene/mod.rs | 2 +- voxygen/src/scene/particle.rs | 60 +++--- voxygen/src/scene/simple.rs | 35 ++-- voxygen/src/scene/terrain.rs | 51 ++++-- voxygen/src/settings.rs | 7 +- voxygen/src/ui/event.rs | 12 +- voxygen/src/ui/mod.rs | 25 ++- world/src/lib.rs | 1 - world/src/sim/erosion.rs | 24 +-- world/src/sim/map.rs | 4 +- .../settlement/building/archetype/house.rs | 16 +- world/src/site/settlement/mod.rs | 7 +- 51 files changed, 559 insertions(+), 583 deletions(-) diff --git a/common/src/comp/agent.rs b/common/src/comp/agent.rs index 93642ab75e..22322b6df0 100644 --- a/common/src/comp/agent.rs +++ b/common/src/comp/agent.rs @@ -43,10 +43,7 @@ impl Alignment { // TODO: Remove this hack pub fn is_friendly_to_players(&self) -> bool { - match self { - Alignment::Npc | Alignment::Tame | Alignment::Owned(_) => true, - _ => false, - } + matches!(self, Alignment::Npc | Alignment::Tame | Alignment::Owned(_)) } } @@ -129,19 +126,9 @@ pub enum Activity { } impl Activity { - pub fn is_follow(&self) -> bool { - match self { - Activity::Follow { .. } => true, - _ => false, - } - } + pub fn is_follow(&self) -> bool { matches!(self, Activity::Follow { .. }) } - pub fn is_attack(&self) -> bool { - match self { - Activity::Attack { .. } => true, - _ => false, - } - } + pub fn is_attack(&self) -> bool { matches!(self, Activity::Attack { .. }) } } impl Default for Activity { diff --git a/common/src/comp/body.rs b/common/src/comp/body.rs index 418ec1fb00..f1fa46e832 100644 --- a/common/src/comp/body.rs +++ b/common/src/comp/body.rs @@ -201,12 +201,7 @@ impl< } impl Body { - pub fn is_humanoid(&self) -> bool { - match self { - Body::Humanoid(_) => true, - _ => false, - } - } + pub fn is_humanoid(&self) -> bool { matches!(self, Body::Humanoid(_)) } // Note: this might need to be refined to something more complex for realistic // behavior with less cylindrical bodies (e.g. wolfs) diff --git a/common/src/comp/character_state.rs b/common/src/comp/character_state.rs index af1d1fac4c..26b490a7f4 100644 --- a/common/src/comp/character_state.rs +++ b/common/src/comp/character_state.rs @@ -73,7 +73,7 @@ pub enum CharacterState { impl CharacterState { pub fn is_wield(&self) -> bool { - match self { + matches!(self, CharacterState::Wielding | CharacterState::BasicMelee(_) | CharacterState::BasicRanged(_) @@ -82,50 +82,37 @@ impl CharacterState { | CharacterState::BasicBlock | CharacterState::LeapMelee(_) | CharacterState::SpinMelee(_) - | CharacterState::ChargedRanged(_) => true, - _ => false, - } + | CharacterState::ChargedRanged(_) + ) } pub fn is_attack(&self) -> bool { - match self { + matches!(self, CharacterState::BasicMelee(_) | CharacterState::BasicRanged(_) | CharacterState::DashMelee(_) | CharacterState::TripleStrike(_) | CharacterState::LeapMelee(_) | CharacterState::SpinMelee(_) - | CharacterState::ChargedRanged(_) => true, - _ => false, - } + | CharacterState::ChargedRanged(_) + ) } pub fn is_aimed(&self) -> bool { - match self { + matches!(self, CharacterState::BasicMelee(_) | CharacterState::BasicRanged(_) | CharacterState::DashMelee(_) | CharacterState::TripleStrike(_) | CharacterState::BasicBlock | CharacterState::LeapMelee(_) - | CharacterState::ChargedRanged(_) => true, - _ => false, - } + | CharacterState::ChargedRanged(_) + ) } - pub fn is_block(&self) -> bool { - match self { - CharacterState::BasicBlock => true, - _ => false, - } - } + pub fn is_block(&self) -> bool { matches!(self, CharacterState::BasicBlock) } - pub fn is_dodge(&self) -> bool { - match self { - CharacterState::Roll(_) => true, - _ => false, - } - } + pub fn is_dodge(&self) -> bool { matches!(self, CharacterState::Roll(_)) } /// Compares for shallow equality (does not check internal struct equality) pub fn same_variant(&self, other: &Self) -> bool { diff --git a/common/src/figure/mat_cell.rs b/common/src/figure/mat_cell.rs index 1a51b1da74..0c066d3681 100644 --- a/common/src/figure/mat_cell.rs +++ b/common/src/figure/mat_cell.rs @@ -25,10 +25,5 @@ pub enum MatCell { impl Vox for MatCell { fn empty() -> Self { MatCell::None } - fn is_empty(&self) -> bool { - match self { - MatCell::None => true, - _ => false, - } - } + fn is_empty(&self) -> bool { matches!(self, MatCell::None) } } diff --git a/common/src/lib.rs b/common/src/lib.rs index b160c0ac60..a52f7116af 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -4,7 +4,6 @@ #![feature( arbitrary_enum_discriminant, const_checked_int_methods, - const_if_match, option_unwrap_none, bool_to_option, label_break_value, diff --git a/common/src/terrain/block.rs b/common/src/terrain/block.rs index e228381c36..72717bd0fb 100644 --- a/common/src/terrain/block.rs +++ b/common/src/terrain/block.rs @@ -201,12 +201,7 @@ impl BlockKind { } } - pub fn is_fluid(&self) -> bool { - match self { - BlockKind::Water => true, - _ => false, - } - } + pub fn is_fluid(&self) -> bool { matches!(self, BlockKind::Water) } pub fn get_glow(&self) -> Option { // TODO: When we have proper volumetric lighting diff --git a/common/src/terrain/map.rs b/common/src/terrain/map.rs index d3efd256c0..e8977fb1bb 100644 --- a/common/src/terrain/map.rs +++ b/common/src/terrain/map.rs @@ -175,11 +175,7 @@ impl MapSizeLg { map_size_lg.y + TERRAIN_CHUNK_BLOCKS_LG < 32; // Assertion on dimensions: product of dimensions must fit in a usize. let chunks_product_in_range = - if let Some(_) = 1usize.checked_shl(map_size_lg.x + map_size_lg.y) { - true - } else { - false - }; + matches!(1usize.checked_shl(map_size_lg.x + map_size_lg.y), Some(_)); if blocks_in_range && chunks_product_in_range { // Cleared all invariants. Ok(MapSizeLg(map_size_lg)) diff --git a/common/src/terrain/structure.rs b/common/src/terrain/structure.rs index 315e70065e..25501a4ac2 100644 --- a/common/src/terrain/structure.rs +++ b/common/src/terrain/structure.rs @@ -32,12 +32,7 @@ pub enum StructureBlock { impl Vox for StructureBlock { fn empty() -> Self { StructureBlock::None } - fn is_empty(&self) -> bool { - match self { - StructureBlock::None => true, - _ => false, - } - } + fn is_empty(&self) -> bool { matches!(self, StructureBlock::None) } } #[derive(Debug)] diff --git a/network/src/lib.rs b/network/src/lib.rs index 51d4e7a1e1..264527c5d3 100644 --- a/network/src/lib.rs +++ b/network/src/lib.rs @@ -1,7 +1,7 @@ #![deny(unsafe_code)] #![cfg_attr(test, deny(rust_2018_idioms))] #![cfg_attr(test, deny(warnings))] -#![feature(try_trait, const_if_match)] +#![feature(try_trait)] //! Crate to handle high level networking of messages with different //! requirements and priorities over a number of protocols diff --git a/network/src/message.rs b/network/src/message.rs index 03626f22d3..d5ab754a26 100644 --- a/network/src/message.rs +++ b/network/src/message.rs @@ -99,43 +99,17 @@ pub(crate) fn partial_eq_io_error(first: &io::Error, second: &io::Error) -> bool } pub(crate) fn partial_eq_bincode(first: &bincode::ErrorKind, second: &bincode::ErrorKind) -> bool { + use bincode::ErrorKind::*; match *first { - bincode::ErrorKind::Io(ref f) => match *second { - bincode::ErrorKind::Io(ref s) => partial_eq_io_error(f, s), - _ => false, - }, - bincode::ErrorKind::InvalidUtf8Encoding(f) => match *second { - bincode::ErrorKind::InvalidUtf8Encoding(s) => f == s, - _ => false, - }, - bincode::ErrorKind::InvalidBoolEncoding(f) => match *second { - bincode::ErrorKind::InvalidBoolEncoding(s) => f == s, - _ => false, - }, - bincode::ErrorKind::InvalidCharEncoding => match *second { - bincode::ErrorKind::InvalidCharEncoding => true, - _ => false, - }, - bincode::ErrorKind::InvalidTagEncoding(f) => match *second { - bincode::ErrorKind::InvalidTagEncoding(s) => f == s, - _ => false, - }, - bincode::ErrorKind::DeserializeAnyNotSupported => match *second { - bincode::ErrorKind::DeserializeAnyNotSupported => true, - _ => false, - }, - bincode::ErrorKind::SizeLimit => match *second { - bincode::ErrorKind::SizeLimit => true, - _ => false, - }, - bincode::ErrorKind::SequenceMustHaveLength => match *second { - bincode::ErrorKind::SequenceMustHaveLength => true, - _ => false, - }, - bincode::ErrorKind::Custom(ref f) => match *second { - bincode::ErrorKind::Custom(ref s) => f == s, - _ => false, - }, + Io(ref f) => matches!(*second, Io(ref s) if partial_eq_io_error(f, s)), + InvalidUtf8Encoding(f) => matches!(*second, InvalidUtf8Encoding(s) if f == s), + InvalidBoolEncoding(f) => matches!(*second, InvalidBoolEncoding(s) if f == s), + InvalidCharEncoding => matches!(*second, InvalidCharEncoding), + InvalidTagEncoding(f) => matches!(*second, InvalidTagEncoding(s) if f == s), + DeserializeAnyNotSupported => matches!(*second, DeserializeAnyNotSupported), + SizeLimit => matches!(*second, SizeLimit), + SequenceMustHaveLength => matches!(*second, SequenceMustHaveLength), + Custom(ref f) => matches!(*second, Custom(ref s) if f == s), } } diff --git a/rust-toolchain b/rust-toolchain index 38819f8f09..0975b06e19 100644 --- a/rust-toolchain +++ b/rust-toolchain @@ -1 +1 @@ -nightly-2020-06-22 +nightly-2020-08-14 diff --git a/server/src/client.rs b/server/src/client.rs index 95e6d96b91..3cb76f659b 100644 --- a/server/src/client.rs +++ b/server/src/client.rs @@ -50,17 +50,17 @@ impl Client { } pub fn is_registered(&self) -> bool { - match self.client_state { - ClientState::Registered | ClientState::Spectator | ClientState::Character => true, - _ => false, - } + matches!( + self.client_state, + ClientState::Registered | ClientState::Spectator | ClientState::Character + ) } pub fn is_ingame(&self) -> bool { - match self.client_state { - ClientState::Spectator | ClientState::Character => true, - _ => false, - } + matches!( + self.client_state, + ClientState::Spectator | ClientState::Character + ) } pub fn allow_state(&mut self, new_state: ClientState) { diff --git a/server/src/events/entity_manipulation.rs b/server/src/events/entity_manipulation.rs index b74ab69cdb..f03fa99906 100644 --- a/server/src/events/entity_manipulation.rs +++ b/server/src/events/entity_manipulation.rs @@ -33,6 +33,15 @@ pub fn handle_damage(server: &Server, uid: Uid, change: HealthChange) { /// other players. If the entity that killed it had stats, then give it exp for /// the kill. Experience given is equal to the level of the entity that was /// killed times 10. +// NOTE: Clippy incorrectly warns about a needless collect here because it does not +// understand that the pet count (which is computed during the first iteration over the +// members in range) is actually used by the second iteration over the members in range; +// since we have no way of knowing the pet count before the first loop finishes, we +// definitely need at least two loops. Then (currently) our only options are to store +// the member list in temporary space (e.g. by collecting to a vector), or to repeat +// the loop; but repeating the loop would currently be very inefficient since it has to +// rescan every entity on the server again. +#[allow(clippy::needless_collect)] pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSource) { let state = server.state_mut(); diff --git a/server/src/events/inventory_manip.rs b/server/src/events/inventory_manip.rs index ca219723f6..a646b561f9 100644 --- a/server/src/events/inventory_manip.rs +++ b/server/src/events/inventory_manip.rs @@ -33,6 +33,7 @@ pub fn snuff_lantern(storage: &mut WriteStorage, entity: Ecs } #[allow(clippy::blocks_in_if_conditions)] +#[allow(clippy::same_item_push)] // TODO: Pending review in #587 pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) { let state = server.state_mut(); let mut dropped_items = Vec::new(); @@ -336,6 +337,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv .and_then(|ldt| slot::loadout_remove(slot, ldt)), }; + // FIXME: We should really require the drop and write to be atomic! if let (Some(item), Some(pos)) = (item, state.ecs().read_storage::().get(entity)) { @@ -362,6 +364,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv let recipe_book = default_recipe_book(); let craft_result = recipe_book.get(&recipe).and_then(|r| r.perform(inv).ok()); + // FIXME: We should really require the drop and write to be atomic! if craft_result.is_some() { let _ = state.ecs().write_storage().insert( entity, diff --git a/server/src/state_ext.rs b/server/src/state_ext.rs index 816b41401c..ac5b8e098f 100644 --- a/server/src/state_ext.rs +++ b/server/src/state_ext.rs @@ -214,7 +214,6 @@ impl StateExt for State { // Notify clients of a player list update let client_uid = self .read_component_cloned::(entity) - .map(|u| u) .expect("Client doesn't have a Uid!!!"); self.notify_registered_clients(ServerMsg::PlayerListUpdate( diff --git a/server/src/sys/entity_sync.rs b/server/src/sys/entity_sync.rs index 2dc20648b4..202592b44b 100644 --- a/server/src/sys/entity_sync.rs +++ b/server/src/sys/entity_sync.rs @@ -338,7 +338,7 @@ impl<'a> System<'a> for Sys { .filter(|o| o.get_pos().and_then(&is_near).unwrap_or(true)) .cloned() .collect::>(); - if outcomes.len() > 0 { + if !outcomes.is_empty() { client.notify(ServerMsg::Outcomes(outcomes)); } } diff --git a/server/src/sys/message.rs b/server/src/sys/message.rs index 0528e8cfba..234f6c0a86 100644 --- a/server/src/sys/message.rs +++ b/server/src/sys/message.rs @@ -192,7 +192,7 @@ impl Sys { }); // Give the player a welcome message - if settings.server_description.len() > 0 { + if !settings.server_description.is_empty() { client.notify( ChatType::CommandInfo .server_msg(settings.server_description.clone()), diff --git a/voxygen/src/audio/mod.rs b/voxygen/src/audio/mod.rs index 5af08f8dc6..5dab15fd4f 100644 --- a/voxygen/src/audio/mod.rs +++ b/voxygen/src/audio/mod.rs @@ -46,19 +46,16 @@ pub struct AudioFrontend { impl AudioFrontend { /// Construct with given device - #[allow(clippy::redundant_clone)] // TODO: Pending review in #587 pub fn new(device: String, max_sfx_channels: usize) -> Self { - let mut sfx_channels = Vec::with_capacity(max_sfx_channels); let audio_device = get_device_raw(&device); + let mut sfx_channels = Vec::with_capacity(max_sfx_channels); if let Some(audio_device) = &audio_device { - for _ in 0..max_sfx_channels { - sfx_channels.push(SfxChannel::new(&audio_device)); - } + sfx_channels.resize_with(max_sfx_channels, || SfxChannel::new(&audio_device)); } Self { - device: device.clone(), + device, device_list: list_devices(), audio_device, sound_cache: SoundCache::default(), diff --git a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs index 088ffe1407..efc36f6997 100644 --- a/voxygen/src/audio/sfx/event_mapper/combat/mod.rs +++ b/voxygen/src/audio/sfx/event_mapper/combat/mod.rs @@ -172,11 +172,7 @@ impl CombatEventMapper { /// ::Equipping to mean the weapon is drawn. This will need updating if the /// animations change to match the wield_duration associated with the weapon fn weapon_drawn(character: &CharacterState) -> bool { - character.is_wield() - || match character { - CharacterState::Equipping { .. } => true, - _ => false, - } + character.is_wield() || matches!(character, CharacterState::Equipping { .. }) } } diff --git a/voxygen/src/hud/chat.rs b/voxygen/src/hud/chat.rs index 2e35de6493..a89a29d62f 100644 --- a/voxygen/src/hud/chat.rs +++ b/voxygen/src/hud/chat.rs @@ -395,10 +395,7 @@ impl<'a> Widget for Chat<'a> { .widget_input(state.ids.chat_input) .presses() .key() - .any(|key_press| match key_press.key { - Key::Return if !state.input.is_empty() => true, - _ => false, - }) + .any(|key_press| matches!(key_press.key, Key::Return if !state.input.is_empty())) { let msg = state.input.clone(); state.update(|s| { diff --git a/voxygen/src/hud/mod.rs b/voxygen/src/hud/mod.rs index 107bd61636..aa2b758106 100644 --- a/voxygen/src/hud/mod.rs +++ b/voxygen/src/hud/mod.rs @@ -482,10 +482,7 @@ impl Show { || self.spell || self.help || self.intro - || match self.open_windows { - Windows::None => false, - _ => true, - } + || !matches!(self.open_windows, Windows::None) { self.bag = false; self.esc_menu = false; @@ -2470,18 +2467,15 @@ impl Hud { // conrod eats tabs. Un-eat a tabstop so tab completion can work if self.ui.ui.global_input().events().any(|event| { use conrod_core::{event, input}; - match event { - //event::Event::Raw(event::Input::Press(input::Button::Keyboard(input::Key::Tab))) - // => true, + matches!(event, + /* event::Event::Raw(event::Input::Press(input::Button::Keyboard(input::Key::Tab))) | */ event::Event::Ui(event::Ui::Press( _, event::Press { button: event::Button::Keyboard(input::Key::Tab), .. }, - )) => true, - _ => false, - } + ))) }) { self.ui .ui diff --git a/voxygen/src/lib.rs b/voxygen/src/lib.rs index 9eeeb17318..ff26ee3f13 100644 --- a/voxygen/src/lib.rs +++ b/voxygen/src/lib.rs @@ -1,6 +1,6 @@ #![deny(unsafe_code)] -#![allow(clippy::option_map_unit_fn)] -#![feature(drain_filter, bool_to_option, or_patterns)] +#![allow(clippy::option_map_unit_fn, incomplete_features)] +#![feature(array_map, bool_to_option, const_generics, drain_filter, or_patterns)] #![recursion_limit = "2048"] #[macro_use] diff --git a/voxygen/src/mesh/greedy.rs b/voxygen/src/mesh/greedy.rs index 93209fa668..6d1ee89368 100644 --- a/voxygen/src/mesh/greedy.rs +++ b/voxygen/src/mesh/greedy.rs @@ -161,22 +161,20 @@ impl<'a> GreedyMesh<'a> { pub fn push( &mut self, config: GreedyConfig, - ) -> Aabb - where + ) where FL: for<'r> FnMut(&'r mut D, Vec3) -> f32 + 'a, FC: for<'r> FnMut(&'r mut D, Vec3) -> Rgb + 'a, FO: for<'r> FnMut(&'r mut D, Vec3) -> bool + 'a, FS: for<'r> FnMut(&'r mut D, Vec3, Vec3, Vec2>) -> Option<(bool, M)>, FP: FnMut(Vec2, Vec2>, Vec3, Vec2>, Vec3, &M), { - let (bounds, cont) = greedy_mesh( + let cont = greedy_mesh( &mut self.atlas, &mut self.col_lights_size, self.max_size, config, ); self.suspended.push(cont); - bounds } /// Finalize the mesh, producing texture color data for the whole model. @@ -219,7 +217,7 @@ fn greedy_mesh<'a, M: PartialEq, D: 'a, FL, FC, FO, FS, FP>( mut should_draw, mut push_quad, }: GreedyConfig, -) -> (Aabb, Box>) +) -> Box> where FL: for<'r> FnMut(&'r mut D, Vec3) -> f32 + 'a, FC: for<'r> FnMut(&'r mut D, Vec3) -> Rgb + 'a, @@ -365,30 +363,23 @@ where }, ); - let bounds = Aabb { - min: Vec3::zero(), - // NOTE: Safe because greedy_size fit in u16. - max: greedy_size.map(|e| e as u16), - }; - ( - bounds, - Box::new(move |col_lights_info| { - let mut data = data; - draw_col_lights( - col_lights_info, - &mut data, - todo_rects, - draw_delta, - get_light, - get_color, - get_opacity, - TerrainVertex::make_col_light, - ); - }), - ) + Box::new(move |col_lights_info| { + let mut data = data; + draw_col_lights( + col_lights_info, + &mut data, + todo_rects, + draw_delta, + get_light, + get_color, + get_opacity, + TerrainVertex::make_col_light, + ); + }) } -// Greedy meshing a single cross-section. +/// Greedy meshing a single cross-section. +// TODO: See if we can speed a lot of this up using SIMD. fn greedy_mesh_cross_section( dims: Vec3, // Should we draw a face here (below this vertex)? If so, provide its meta information. @@ -524,9 +515,10 @@ fn add_to_atlas( /// We deferred actually recording the colors within the rectangles in order to /// generate a texture of minimal size; we now proceed to create and populate /// it. -/// -/// TODO: Consider using the heavier interface (not the simple one) which seems -/// to provide builtin support for what we're doing here. +// TODO: Consider using the heavier interface (not the simple one) which seems +// to provide builtin support for what we're doing here. +// +// TODO: See if we can speed this up using SIMD. fn draw_col_lights( (col_lights, cur_size): &mut ColLightInfo, data: &mut D, @@ -610,6 +602,7 @@ fn draw_col_lights( /// Precondition: when this function is called, atlas_pos should reflect an /// actual valid position in a texture atlas (meaning it should fit into a u16). +// TODO: See if we can speed a lot of this up using SIMD. fn create_quad_greedy( origin: Vec3, dim: Vec2, diff --git a/voxygen/src/mesh/segment.rs b/voxygen/src/mesh/segment.rs index 0ed2aaad8d..62ff06078b 100644 --- a/voxygen/src/mesh/segment.rs +++ b/voxygen/src/mesh/segment.rs @@ -7,11 +7,13 @@ use crate::{ self, FigurePipeline, Mesh, ParticlePipeline, ShadowPipeline, SpritePipeline, TerrainPipeline, }, + scene::math, }; use common::{ figure::Cell, vol::{BaseVol, ReadVol, SizedVol, Vox}, }; +use core::ops::Range; use vek::*; type SpriteVertex = ::Vertex; @@ -26,15 +28,31 @@ where * &'a V: BaseVol, */ { type Pipeline = TerrainPipeline; - type Result = Aabb; + /// NOTE: The result provides the (roughly) computed bounds for the model, + /// and the vertex range meshed for this model; we return this instead + /// of the full opaque mesh so we can avoid allocating a separate mesh + /// for each bone. + /// + /// Later, we can iterate through the bone array and correctly assign bone + /// ids to all vertices in range for each segment. + /// + /// FIXME: A refactor of the figure cache to not just return an array of + /// models (thus allowing us to knoe the bone index ahead of time) would + /// avoid needing per-bone information at all. + type Result = (math::Aabb, Range); type ShadowPipeline = ShadowPipeline; - type Supplement = (&'b mut GreedyMesh<'a>, Vec3, Vec3); + type Supplement = ( + &'b mut GreedyMesh<'a>, + &'b mut Mesh, + Vec3, + Vec3, + ); type TranslucentPipeline = FigurePipeline; #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn generate_mesh( self, - (greedy, offs, scale): Self::Supplement, + (greedy, opaque_mesh, offs, scale): Self::Supplement, ) -> MeshGen, Self> { let max_size = greedy.max_size(); // NOTE: Required because we steal two bits from the normal in the shadow uint @@ -43,17 +61,21 @@ where // coordinate instead of 1 << 16. assert!(max_size.width.max(max_size.height) < 1 << 15); - let greedy_size = Vec3::new( - (self.upper_bound().x - self.lower_bound().x + 1) as usize, - (self.upper_bound().y - self.lower_bound().y + 1) as usize, - (self.upper_bound().z - self.lower_bound().z + 1) as usize, + let lower_bound = self.lower_bound(); + let upper_bound = self.upper_bound(); + assert!( + lower_bound.x <= upper_bound.x + && lower_bound.y <= upper_bound.y + && lower_bound.z <= upper_bound.z ); + // NOTE: Figure sizes should be no more than 512 along each axis. + let greedy_size = upper_bound - lower_bound + 1; + assert!(greedy_size.x <= 512 && greedy_size.y <= 512 && greedy_size.z <= 512); + // NOTE: Cast to usize is safe because of previous check, since all values fit + // into u16 which is safe to cast to usize. + let greedy_size = greedy_size.as_::(); let greedy_size_cross = greedy_size; - let draw_delta = Vec3::new( - self.lower_bound().x, - self.lower_bound().y, - self.lower_bound().z, - ); + let draw_delta = lower_bound; let get_light = |vol: &mut V, pos: Vec3| { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { @@ -79,8 +101,8 @@ where TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, 0) }; - let mut opaque_mesh = Mesh::new(); - let bounds = greedy.push(GreedyConfig { + let start = opaque_mesh.vertices().len(); + greedy.push(GreedyConfig { data: self, draw_delta, greedy_size, @@ -101,14 +123,20 @@ where )); }, }); - let bounds = bounds.map(f32::from); - let bounds = Aabb { - min: (bounds.min + offs) * scale, - max: (bounds.max + offs) * scale, + let bounds = math::Aabb { + // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16. + min: math::Vec3::from((lower_bound.as_::() + offs) * scale), + max: math::Vec3::from((upper_bound.as_::() + offs) * scale), } .made_valid(); + let vertex_range = start..opaque_mesh.vertices().len(); - (opaque_mesh, Mesh::new(), Mesh::new(), bounds) + ( + Mesh::new(), + Mesh::new(), + Mesh::new(), + (bounds, vertex_range), + ) } } @@ -122,13 +150,13 @@ where type Pipeline = SpritePipeline; type Result = (); type ShadowPipeline = ShadowPipeline; - type Supplement = (&'b mut GreedyMesh<'a>, bool); + type Supplement = (&'b mut GreedyMesh<'a>, &'b mut Mesh, bool); type TranslucentPipeline = SpritePipeline; #[allow(clippy::or_fun_call)] // TODO: Pending review in #587 fn generate_mesh( self, - (greedy, vertical_stripes): Self::Supplement, + (greedy, opaque_mesh, vertical_stripes): Self::Supplement, ) -> MeshGen, Self> { let max_size = greedy.max_size(); // NOTE: Required because we steal two bits from the normal in the shadow uint @@ -137,22 +165,25 @@ where // coordinate instead of 1 << 16. assert!(max_size.width.max(max_size.height) < 1 << 16); - let greedy_size = Vec3::new( - (self.upper_bound().x - self.lower_bound().x + 1) as usize, - (self.upper_bound().y - self.lower_bound().y + 1) as usize, - (self.upper_bound().z - self.lower_bound().z + 1) as usize, + let lower_bound = self.lower_bound(); + let upper_bound = self.upper_bound(); + assert!( + lower_bound.x <= upper_bound.x + && lower_bound.y <= upper_bound.y + && lower_bound.z <= upper_bound.z ); + let greedy_size = upper_bound - lower_bound + 1; assert!( greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, "Sprite size out of bounds: {:?} ≤ (15, 15, 63)", greedy_size - 1 ); + // NOTE: Cast to usize is safe because of previous check, since all values fit + // into u16 which is safe to cast to usize. + let greedy_size = greedy_size.as_::(); + let greedy_size_cross = greedy_size; - let draw_delta = Vec3::new( - self.lower_bound().x, - self.lower_bound().y, - self.lower_bound().z, - ); + let draw_delta = lower_bound; let get_light = |vol: &mut V, pos: Vec3| { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { @@ -177,8 +208,7 @@ where let create_opaque = |atlas_pos, pos: Vec3, norm, _meta| SpriteVertex::new(atlas_pos, pos, norm); - let mut opaque_mesh = Mesh::new(); - let _bounds = greedy.push(GreedyConfig { + greedy.push(GreedyConfig { data: self, draw_delta, greedy_size, @@ -200,7 +230,7 @@ where }, }); - (opaque_mesh, Mesh::new(), Mesh::new(), ()) + (Mesh::new(), Mesh::new(), Mesh::new(), ()) } } @@ -229,22 +259,25 @@ where // coordinate instead of 1 << 16. assert!(max_size.width.max(max_size.height) < 1 << 16); - let greedy_size = Vec3::new( - (self.upper_bound().x - self.lower_bound().x + 1) as usize, - (self.upper_bound().y - self.lower_bound().y + 1) as usize, - (self.upper_bound().z - self.lower_bound().z + 1) as usize, + let lower_bound = self.lower_bound(); + let upper_bound = self.upper_bound(); + assert!( + lower_bound.x <= upper_bound.x + && lower_bound.y <= upper_bound.y + && lower_bound.z <= upper_bound.z ); + let greedy_size = upper_bound - lower_bound + 1; assert!( greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, - "Sprite size out of bounds: {:?} ≤ (15, 15, 63)", + "Particle size out of bounds: {:?} ≤ (15, 15, 63)", greedy_size - 1 ); + // NOTE: Cast to usize is safe because of previous check, since all values fit + // into u16 which is safe to cast to usize. + let greedy_size = greedy_size.as_::(); + let greedy_size_cross = greedy_size; - let draw_delta = Vec3::new( - self.lower_bound().x, - self.lower_bound().y, - self.lower_bound().z, - ); + let draw_delta = lower_bound; let get_light = |vol: &mut V, pos: Vec3| { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { @@ -269,7 +302,7 @@ where let create_opaque = |_atlas_pos, pos: Vec3, norm| ParticleVertex::new(pos, norm); let mut opaque_mesh = Mesh::new(); - let _bounds = greedy.push(GreedyConfig { + greedy.push(GreedyConfig { data: self, draw_delta, greedy_size, @@ -304,7 +337,7 @@ fn should_draw_greedy( let from = flat_get(pos - delta); let to = flat_get(pos); let from_opaque = !from.is_empty(); - if from_opaque == !to.is_empty() { + if from_opaque != to.is_empty() { None } else { // If going from transparent to opaque, backward facing; otherwise, forward @@ -323,7 +356,7 @@ fn should_draw_greedy_ao( let from = flat_get(pos - delta); let to = flat_get(pos); let from_opaque = !from.is_empty(); - if from_opaque == !to.is_empty() { + if from_opaque != to.is_empty() { None } else { let faces_forward = from_opaque; diff --git a/voxygen/src/mesh/terrain.rs b/voxygen/src/mesh/terrain.rs index de572e7457..7baf4858eb 100644 --- a/voxygen/src/mesh/terrain.rs +++ b/voxygen/src/mesh/terrain.rs @@ -7,7 +7,7 @@ use crate::{ }; use common::{ terrain::{Block, BlockKind}, - vol::{DefaultVolIterator, ReadVol, RectRasterableVol, Vox}, + vol::{ReadVol, RectRasterableVol, Vox}, volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d}, }; use std::{collections::VecDeque, fmt::Debug}; @@ -40,7 +40,7 @@ impl Blendable for BlockKind { } const SUNLIGHT: u8 = 24; -const MAX_LIGHT_DIST: i32 = SUNLIGHT as i32; +const _MAX_LIGHT_DIST: i32 = SUNLIGHT as i32; fn calc_light + ReadVol + Debug>( bounds: Aabb, @@ -241,9 +241,10 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> (range, max_texture_size): Self::Supplement, ) -> MeshGen { // Find blocks that should glow - let lit_blocks = - DefaultVolIterator::new(self, range.min - MAX_LIGHT_DIST, range.max + MAX_LIGHT_DIST) - .filter_map(|(pos, block)| block.get_glow().map(|glow| (pos, glow))); + // FIXME: Replace with real lit blocks when we actually have blocks that glow. + let lit_blocks = core::iter::empty(); + /* DefaultVolIterator::new(self, range.min - MAX_LIGHT_DIST, range.max + MAX_LIGHT_DIST) + .filter_map(|(pos, block)| block.get_glow().map(|glow| (pos, glow))); */ // Calculate chunk lighting let mut light = calc_light(range, self, lit_blocks); @@ -327,11 +328,18 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> let max_size = guillotiere::Size::new(i32::from(max_texture_size.x), i32::from(max_texture_size.y)); - let greedy_size = Vec3::new( - (range.size().w - 2) as usize, - (range.size().h - 2) as usize, - (z_end - z_start + 1) as usize, - ); + let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1); + // NOTE: Terrain sizes are limited to 32 x 32 x 16384 (to fit in 24 bits: 5 + 5 + // + 14). FIXME: Make this function fallible, since the terrain + // information might be dynamically generated which would make this hard + // to enforce. + assert!(greedy_size.x <= 32 && greedy_size.y <= 32 && greedy_size.z <= 16384); + // NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16, + // which always fits into a f32. + let max_bounds: Vec3 = greedy_size.as_::(); + // NOTE: Cast is safe by prior assertion on greedy_size; it fits into a u16, + // which always fits into a usize. + let greedy_size = greedy_size.as_::(); let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z); let draw_delta = Vec3::new(1, 1, z_start); @@ -353,7 +361,7 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> let mut greedy = GreedyMesh::new(max_size); let mut opaque_mesh = Mesh::new(); let mut fluid_mesh = Mesh::new(); - let bounds = greedy.push(GreedyConfig { + greedy.push(GreedyConfig { data: (), draw_delta, greedy_size, @@ -388,10 +396,11 @@ impl<'a, V: RectRasterableVol + ReadVol + Debug> }, }); - let bounds = bounds.map(f32::from); + let min_bounds = mesh_delta; let bounds = Aabb { - min: bounds.min + mesh_delta, - max: bounds.max + mesh_delta, + // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16. + min: min_bounds, + max: max_bounds + min_bounds, }; let (col_lights, col_lights_size) = greedy.finalize(); diff --git a/voxygen/src/render/consts.rs b/voxygen/src/render/consts.rs index 1e410d3179..0eded13428 100644 --- a/voxygen/src/render/consts.rs +++ b/voxygen/src/render/consts.rs @@ -25,12 +25,12 @@ impl Consts { vals: &[T], offset: usize, ) -> Result<(), RenderError> { - if vals.len() > 0 { + if vals.is_empty() { + Ok(()) + } else { encoder .update_buffer(&self.buf, vals, offset) .map_err(RenderError::UpdateError) - } else { - Ok(()) } } } diff --git a/voxygen/src/render/instances.rs b/voxygen/src/render/instances.rs index c99be74ffa..c53d5ee2c2 100644 --- a/voxygen/src/render/instances.rs +++ b/voxygen/src/render/instances.rs @@ -15,7 +15,7 @@ impl Instances { pub fn new(factory: &mut gfx_backend::Factory, len: usize) -> Result { Ok(Self { ibuf: factory - .create_buffer(len, Role::Vertex, Usage::Dynamic, Bind::TRANSFER_DST) + .create_buffer(len, Role::Vertex, Usage::Dynamic, Bind::empty()) .map_err(RenderError::BufferCreationError)?, }) } diff --git a/voxygen/src/render/mesh.rs b/voxygen/src/render/mesh.rs index 0d4a0d2165..4ddbe65843 100644 --- a/voxygen/src/render/mesh.rs +++ b/voxygen/src/render/mesh.rs @@ -1,5 +1,5 @@ use super::Pipeline; -use std::iter::FromIterator; +use core::{iter::FromIterator, ops::Range}; /// A `Vec`-based mesh structure used to store mesh data on the CPU. pub struct Mesh { @@ -69,6 +69,11 @@ impl Mesh

{ } pub fn iter(&self) -> std::slice::Iter { self.verts.iter() } + + /// NOTE: Panics if vertex_range is out of bounds of vertices. + pub fn iter_mut(&mut self, vertex_range: Range) -> std::slice::IterMut { + self.verts[vertex_range].iter_mut() + } } impl IntoIterator for Mesh

{ diff --git a/voxygen/src/render/mod.rs b/voxygen/src/render/mod.rs index ea77ff2520..74bac19f19 100644 --- a/voxygen/src/render/mod.rs +++ b/voxygen/src/render/mod.rs @@ -244,7 +244,7 @@ impl core::convert::TryFrom for ShadowMapMode { } impl ShadowMode { - pub fn is_map(&self) -> bool { if let Self::Map(_) = self { true } else { false } } + pub fn is_map(&self) -> bool { matches!(self, Self::Map(_)) } } /// Render modes diff --git a/voxygen/src/render/model.rs b/voxygen/src/render/model.rs index d994215d1d..119f314feb 100644 --- a/voxygen/src/render/model.rs +++ b/voxygen/src/render/model.rs @@ -22,6 +22,15 @@ impl Model

{ } pub fn vertex_range(&self) -> Range { self.vertex_range.clone() } + + /// Create a model with a slice of a portion of this model to send to the + /// renderer. + pub fn submodel(&self, vertex_range: Range) -> Model

{ + Model { + vbuf: self.vbuf.clone(), + vertex_range, + } + } } /// Represents a mesh on the GPU which can be updated dynamically. @@ -40,10 +49,10 @@ impl DynamicModel

{ /// Create a model with a slice of a portion of this model to send to the /// renderer. - pub fn submodel(&self, range: Range) -> Model

{ + pub fn submodel(&self, vertex_range: Range) -> Model

{ Model { vbuf: self.vbuf.clone(), - vertex_range: range.start as u32..range.end as u32, + vertex_range, } } diff --git a/voxygen/src/render/pipelines/figure.rs b/voxygen/src/render/pipelines/figure.rs index a81a3455c9..65a1af932f 100644 --- a/voxygen/src/render/pipelines/figure.rs +++ b/voxygen/src/render/pipelines/figure.rs @@ -1,11 +1,9 @@ use super::{ - super::{ - ColLightFmt, Mesh, Model, Pipeline, TerrainPipeline, Texture, TgtColorFmt, - TgtDepthStencilFmt, - }, + super::{Mesh, Model, Pipeline, TerrainPipeline, TgtColorFmt, TgtDepthStencilFmt}, shadow, Globals, Light, Shadow, }; use crate::mesh::greedy::GreedyMesh; +use core::ops::Range; use gfx::{ self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, gfx_pipeline_inner, gfx_vertex_struct_meta, state::ColorMask, @@ -117,12 +115,9 @@ impl Pipeline for FigurePipeline { } pub struct FigureModel { - pub bounds: Aabb, pub opaque: Model, - // TODO: Consider using mipmaps instead of storing multiple texture atlases for different LOD - // levels. - pub col_lights: Texture, - pub allocation: guillotiere::Allocation, + /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different + * LOD levels. */ } impl FigureModel { @@ -137,7 +132,4 @@ impl FigureModel { } } -pub type BoneMeshes = ( - Mesh, /* , Mesh */ - Aabb, -); +pub type BoneMeshes = (Mesh, (anim::vek::Aabb, Range)); diff --git a/voxygen/src/render/pipelines/shadow.rs b/voxygen/src/render/pipelines/shadow.rs index 2333de29b8..cee7a2fd1e 100644 --- a/voxygen/src/render/pipelines/shadow.rs +++ b/voxygen/src/render/pipelines/shadow.rs @@ -7,15 +7,11 @@ use super::{ }; use gfx::{ self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, - gfx_pipeline_inner, gfx_vertex_struct_meta, + gfx_pipeline_inner, }; use vek::*; gfx_defines! { - vertex Vertex { - pos_norm: u32 = "v_pos_norm", - } - constant Locals { shadow_matrices: [[f32; 4]; 4] = "shadowMatrices", texture_mats: [[f32; 4]; 4] = "texture_mat", @@ -55,54 +51,6 @@ gfx_defines! { } } -impl Vertex { - pub fn new(pos: Vec3, norm: Vec3, meta: bool) -> Self { - let norm_bits = if norm.x != 0.0 { - if norm.x < 0.0 { 0 } else { 1 } - } else if norm.y != 0.0 { - if norm.y < 0.0 { 2 } else { 3 } - } else if norm.z < 0.0 { - 4 - } else { - 5 - }; - - const EXTRA_NEG_Z: f32 = 32768.0; - - Self { - pos_norm: 0 - | ((pos.x as u32) & 0x003F) << 0 - | ((pos.y as u32) & 0x003F) << 6 - | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12 - | if meta { 1 } else { 0 } << 28 - | (norm_bits & 0x7) << 29, - } - } - - pub fn new_figure(pos: Vec3, norm: Vec3, bone_idx: u8) -> Self { - let norm_bits = if norm.x.min(norm.y).min(norm.z) < 0.0 { - 0 - } else { - 1 - }; - Self { - pos_norm: pos - .map2(Vec3::new(0, 9, 18), |e, shift| { - (((e * 2.0 + 256.0) as u32) & 0x1FF) << shift - }) - .reduce_bitor() - | (((bone_idx & 0xF) as u32) << 27) - | (norm_bits << 31), - } - } - - pub fn with_bone_idx(self, bone_idx: u8) -> Self { - Self { - pos_norm: (self.pos_norm & !(0xF << 27)) | ((bone_idx as u32 & 0xF) << 27), - } - } -} - impl Locals { pub fn new(shadow_mat: Mat4, texture_mat: Mat4) -> Self { Self { @@ -138,5 +86,5 @@ impl ShadowPipeline { } impl Pipeline for ShadowPipeline { - type Vertex = Vertex; + type Vertex = terrain::Vertex; } diff --git a/voxygen/src/render/pipelines/sprite.rs b/voxygen/src/render/pipelines/sprite.rs index 4580212eb3..9ac8ed369f 100644 --- a/voxygen/src/render/pipelines/sprite.rs +++ b/voxygen/src/render/pipelines/sprite.rs @@ -101,16 +101,13 @@ impl Vertex { }; Self { - // pos_norm: 0 - // | ((pos.x as u32) & 0x003F) << 0 + // pos_norm: ((pos.x as u32) & 0x003F) // | ((pos.y as u32) & 0x003F) << 6 // | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12 // | if meta { 1 } else { 0 } << 28 // | (norm_bits & 0x7) << 29, pos: pos.into_array(), - atlas_pos: 0 - | ((atlas_pos.x as u32) & 0xFFFF) << 0 - | ((atlas_pos.y as u32) & 0xFFFF) << 16, + atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | ((atlas_pos.y as u32) & 0xFFFF) << 16, norm_ao: norm_bits, } } @@ -122,8 +119,7 @@ impl Instance { let mat_arr = mat.into_col_arrays(); Self { - pos_ori: 0 - | ((pos.x as u32) & 0x003F) << 0 + pos_ori: ((pos.x as u32) & 0x003F) | ((pos.y as u32) & 0x003F) << 6 | (((pos + EXTRA_NEG_Z).z.max(0).min(1 << 16) as u32) & 0xFFFF) << 12 | (u32::from(ori_bits) & 0x7) << 29, diff --git a/voxygen/src/render/pipelines/terrain.rs b/voxygen/src/render/pipelines/terrain.rs index 3f92daeb2c..7fca8f0dbb 100644 --- a/voxygen/src/render/pipelines/terrain.rs +++ b/voxygen/src/render/pipelines/terrain.rs @@ -61,15 +61,12 @@ impl Vertex { 5 }; Self { - pos_norm: 0 - | ((pos.x as u32) & 0x003F) << 0 + pos_norm: ((pos.x as u32) & 0x003F) << 0 | ((pos.y as u32) & 0x003F) << 6 | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12 | if meta { 1 } else { 0 } << 28 | (norm_bits & 0x7) << 29, - atlas_pos: 0 - | ((atlas_pos.x as u32) & 0xFFFF) << 0 - | ((atlas_pos.y as u32) & 0xFFFF) << 16, + atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) << 0 | ((atlas_pos.y as u32) & 0xFFFF) << 16, } } @@ -94,8 +91,7 @@ impl Vertex { .reduce_bitor() | (((bone_idx & 0xF) as u32) << 27) | (norm_bits << 31), - atlas_pos: 0 - | ((atlas_pos.x as u32) & 0x7FFF) << 2 + atlas_pos: ((atlas_pos.x as u32) & 0x7FFF) << 2 | ((atlas_pos.y as u32) & 0x7FFF) << 17 | axis_bits & 3, } @@ -109,11 +105,9 @@ impl Vertex { [col.r, col.g, col.b, light] } - pub fn with_bone_idx(self, bone_idx: u8) -> Self { - Self { - pos_norm: (self.pos_norm & !(0xF << 27)) | ((bone_idx as u32 & 0xF) << 27), - ..self - } + /// Set the bone_idx for an existing figure vertex. + pub fn set_bone_idx(&mut self, bone_idx: u8) { + self.pos_norm = (self.pos_norm & !(0xF << 27)) | ((bone_idx as u32 & 0xF) << 27); } } diff --git a/voxygen/src/render/renderer.rs b/voxygen/src/render/renderer.rs index dccac0f06f..36ab06c02c 100644 --- a/voxygen/src/render/renderer.rs +++ b/voxygen/src/render/renderer.rs @@ -1002,7 +1002,7 @@ impl Renderer { pub fn render_figure( &mut self, model: &figure::FigureModel, - _col_lights: &Texture, + col_lights: &Texture, global: &GlobalModel, locals: &Consts, bones: &Consts, @@ -1026,7 +1026,6 @@ impl Renderer { (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; - let col_lights = &model.col_lights; let model = &model.opaque; self.encoder.draw( @@ -1087,10 +1086,7 @@ impl Renderer { (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; - let col_lights = &model.col_lights; let model = &model.opaque; - // let atlas_model = &model.opaque; - // let model = &model.shadow; self.encoder.draw( &gfx::Slice { @@ -1103,9 +1099,7 @@ impl Renderer { &self.player_shadow_pipeline.pso, &figure::pipe::Data { vbuf: model.vbuf.clone(), - // abuf: atlas_model.vbuf.clone(), col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), - // col_lights: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()), locals: locals.buf.clone(), globals: global.globals.buf.clone(), bones: bones.buf.clone(), @@ -1127,7 +1121,7 @@ impl Renderer { pub fn render_player( &mut self, model: &figure::FigureModel, - _col_lights: &Texture, + col_lights: &Texture, global: &GlobalModel, locals: &Consts, bones: &Consts, @@ -1151,7 +1145,6 @@ impl Renderer { (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), ) }; - let col_lights = &model.col_lights; let model = &model.opaque; self.encoder.draw( @@ -1476,6 +1469,10 @@ impl Renderer { ibuf: instances.ibuf.clone(), col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), terrain_locals: terrain_locals.buf.clone(), + // NOTE: It would be nice if this wasn't needed and we could use a constant buffer + // offset into the sprite data. Hopefully, when we switch to wgpu we can do this, + // as it offers the exact API we want (the equivalent can be done in OpenGL using + // glBindBufferOffset). locals: locals.buf.clone(), globals: global.globals.buf.clone(), lights: global.lights.buf.clone(), @@ -1581,7 +1578,7 @@ impl Renderer { /// Queue the rendering of the provided UI element in the upcoming frame. pub fn render_ui_element>( &mut self, - model: &Model, + model: Model, tex: &Texture, scissor: Aabr, globals: &Consts, @@ -1594,15 +1591,15 @@ impl Renderer { let Aabr { min, max } = scissor; self.encoder.draw( &gfx::Slice { - start: model.vertex_range().start, - end: model.vertex_range().end, + start: model.vertex_range.start, + end: model.vertex_range.end, base_vertex: 0, instances: None, buffer: gfx::IndexBuffer::Auto, }, &self.ui_pipeline.pso, &ui::pipe::Data { - vbuf: model.vbuf.clone(), + vbuf: model.vbuf, scissor: gfx::Rect { x: min.x, y: min.y, diff --git a/voxygen/src/scene/figure/cache.rs b/voxygen/src/scene/figure/cache.rs index 07f6157151..d2e030f47a 100644 --- a/voxygen/src/scene/figure/cache.rs +++ b/voxygen/src/scene/figure/cache.rs @@ -1,7 +1,7 @@ -use super::load::*; +use super::{load::*, FigureModelEntry}; use crate::{ mesh::{greedy::GreedyMesh, Meshable}, - render::{BoneMeshes, FigureModel, FigurePipeline, Mesh, Renderer}, + render::{BoneMeshes, FigureModel, FigurePipeline, Mesh, Renderer, TerrainPipeline}, scene::camera::CameraMode, }; use anim::Skeleton; @@ -22,7 +22,7 @@ use core::convert::TryInto; use hashbrown::{hash_map::Entry, HashMap}; use vek::*; -pub type FigureModelEntry = [FigureModel; 3]; +pub type FigureModelEntryLod = FigureModelEntry<3>; #[derive(Eq, Hash, PartialEq)] struct FigureKey { @@ -198,7 +198,7 @@ pub struct FigureModelCache where Skel: Skeleton, { - models: HashMap, + models: HashMap, manifest_indicator: ReloadIndicator, } @@ -980,7 +980,7 @@ impl FigureModelCache { tick: u64, camera_mode: CameraMode, character_state: Option<&CharacterState>, - ) -> &(FigureModelEntry, Skel::Attr) + ) -> &(FigureModelEntryLod, Skel::Attr) where for<'a> &'a common::comp::Body: std::convert::TryInto, Skel::Attr: Default, @@ -1011,78 +1011,137 @@ impl FigureModelCache { .unwrap_or_else(::default); let manifest_indicator = &mut self.manifest_indicator; - let mut make_model = - |generate_mesh: for<'a> fn(&mut GreedyMesh<'a>, _, _) -> _| { - let mut greedy = FigureModel::make_greedy(); - let mut opaque = Mesh::new(); - let mut figure_bounds = Aabb { - min: Vec3::zero(), - max: Vec3::zero(), - }; + let mut greedy = FigureModel::make_greedy(); + let mut opaque = Mesh::::new(); + // Choose the most conservative bounds for any LOD model. + let mut figure_bounds = anim::vek::Aabb { + min: anim::vek::Vec3::zero(), + max: anim::vek::Vec3::zero(), + }; + // Meshes all bone models for this figure using the given mesh generation + // function, attaching it to the current greedy mesher and opaque vertex + // list. Returns the vertex bounds of the meshed model within the opaque + // mesh. + let mut make_model = |generate_mesh: for<'a, 'b> fn( + &mut GreedyMesh<'a>, + &'b mut _, + _, + _, + ) + -> _| { + let vertex_start = opaque.vertices().len(); + let meshes = Self::bone_meshes(key, manifest_indicator, |segment, offset| { - generate_mesh(&mut greedy, segment, offset) - }) + generate_mesh(&mut greedy, &mut opaque, segment, offset) + }); + meshes .iter() .enumerate() - .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm))) - .for_each(|(i, (opaque_mesh, bounds))| { + // NOTE: Cast to u8 is safe because i <= 16. + .filter_map(|(i, bm)| bm.as_ref().map(|bm| (i as u8, bm.clone()))) + .for_each(|(i, (_opaque_mesh, (bounds, vertex_range)))| { + // Update the bone index for all vertices that belong ot this + // model. opaque - .push_mesh_map(opaque_mesh, |vert| vert.with_bone_idx(i as u8)); - figure_bounds.expand_to_contain(*bounds); + .iter_mut(vertex_range) + .for_each(|vert| { + vert.set_bone_idx(i); + }); + + // Update the figure bounds to the largest granularity seen so far + // (NOTE: this is more than a little imeprfect). + // + // FIXME: Maybe use the default bone position in the idle animation + // to figure this out instead? + figure_bounds.expand_to_contain(bounds); }); - col_lights - .create_figure(renderer, greedy, (opaque, figure_bounds)) - .unwrap() - }; + // NOTE: vertex_start and vertex_end *should* fit in a u32, by the + // following logic: + // + // Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8. + // This uses at most 24 bits to store every vertex exactly once. + // Greedy meshing can store each vertex in up to 3 quads, we have 3 + // greedy models, and we store 1.5x the vertex count, so the maximum + // total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding + // up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a + // u32. + // + // (We could also, though we prefer not to, reason backwards from the + // maximum figure texture size of 2^15 × 2^15, also fits in a u32; we + // can also see that, since we can have at most one texture entry per + // vertex, any texture atlas of size 2^14 × 2^14 or higher should be + // able to store data for any figure. So the only reason we would fail + // here would be if the user's computer could not store a texture large + // enough to fit all the LOD models for the figure, not for fundamental + // reasonS related to fitting in a u32). + // + // Therefore, these casts are safe. + vertex_start as u32..opaque.vertices().len() as u32 + }; fn generate_mesh<'a>( greedy: &mut GreedyMesh<'a>, + opaque_mesh: &mut Mesh, segment: Segment, offset: Vec3, ) -> BoneMeshes { let (opaque, _, _, bounds) = Meshable::::generate_mesh( segment, - (greedy, offset, Vec3::one()), + (greedy, opaque_mesh, offset, Vec3::one()), ); (opaque, bounds) } fn generate_mesh_lod_mid<'a>( greedy: &mut GreedyMesh<'a>, + opaque_mesh: &mut Mesh, segment: Segment, offset: Vec3, ) -> BoneMeshes { - let lod_scale = Vec3::broadcast(0.6); + let lod_scale = 0.6; let (opaque, _, _, bounds) = Meshable::::generate_mesh( - segment.scaled_by(lod_scale), - (greedy, offset * lod_scale, Vec3::one() / lod_scale), + segment.scaled_by(Vec3::broadcast(lod_scale)), + ( + greedy, + opaque_mesh, + offset * lod_scale, + Vec3::one() / lod_scale, + ), ); (opaque, bounds) } fn generate_mesh_lod_low<'a>( greedy: &mut GreedyMesh<'a>, + opaque_mesh: &mut Mesh, segment: Segment, offset: Vec3, ) -> BoneMeshes { - let lod_scale = Vec3::broadcast(0.3); - let segment = segment.scaled_by(lod_scale); + let lod_scale = 0.3; let (opaque, _, _, bounds) = Meshable::::generate_mesh( - segment, - (greedy, offset * lod_scale, Vec3::one() / lod_scale), + segment.scaled_by(Vec3::broadcast(lod_scale)), + ( + greedy, + opaque_mesh, + offset * lod_scale, + Vec3::one() / lod_scale, + ), ); (opaque, bounds) } + let models = [ + make_model(generate_mesh), + make_model(generate_mesh_lod_mid), + make_model(generate_mesh_lod_low), + ]; ( - [ - make_model(generate_mesh), - make_model(generate_mesh_lod_mid), - make_model(generate_mesh_lod_low), - ], + col_lights + .create_figure(renderer, greedy, (opaque, figure_bounds), models) + .expect("Failed to upload figure data to the GPU!"), skeleton_attr, ) }; @@ -1100,14 +1159,12 @@ impl FigureModelCache { } // TODO: Don't hard-code this. if tick % 60 == 0 { - self.models.retain(|_, ((models, _), last_used)| { + self.models.retain(|_, ((model_entry, _), last_used)| { // Wait about a minute at 60 fps before invalidating old models. let delta = 60 * 60; let alive = *last_used + delta > tick; if !alive { - models.iter().for_each(|model| { - col_lights.atlas.deallocate(model.allocation.id); - }); + col_lights.atlas.deallocate(model_entry.allocation.id); } alive }); diff --git a/voxygen/src/scene/figure/load.rs b/voxygen/src/scene/figure/load.rs index 457bbaab56..aa3d34d107 100644 --- a/voxygen/src/scene/figure/load.rs +++ b/voxygen/src/scene/figure/load.rs @@ -760,7 +760,7 @@ impl HumMainWeaponSpec { let tool_kind = if let Some(kind) = tool_kind { kind } else { - return (Mesh::new(), Aabb::default()); + return (Mesh::new(), (anim::vek::Aabb::default(), 0..0)); }; let spec = match self.0.get(tool_kind) { diff --git a/voxygen/src/scene/figure/mod.rs b/voxygen/src/scene/figure/mod.rs index bcca7c7664..8294a2ca5a 100644 --- a/voxygen/src/scene/figure/mod.rs +++ b/voxygen/src/scene/figure/mod.rs @@ -8,8 +8,8 @@ use crate::{ ecs::comp::Interpolated, mesh::greedy::GreedyMesh, render::{ - BoneMeshes, ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, GlobalModel, - RenderError, Renderer, ShadowPipeline, Texture, + ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, GlobalModel, Mesh, + RenderError, Renderer, ShadowPipeline, TerrainPipeline, Texture, }, scene::{ camera::{Camera, CameraMode, Dependents}, @@ -36,8 +36,9 @@ use common::{ }; use core::{ borrow::Borrow, + convert::TryFrom, hash::Hash, - ops::{Deref, DerefMut}, + ops::{Deref, DerefMut, Range}, }; use guillotiere::AtlasAllocator; use hashbrown::HashMap; @@ -51,6 +52,33 @@ const MOVING_THRESHOLD_SQR: f32 = MOVING_THRESHOLD * MOVING_THRESHOLD; /// camera data, fiigure LOD render distance. pub type CameraData<'a> = (&'a Camera, f32); +/// Enough data to render a figure model. +pub type FigureModelRef<'a> = ( + &'a Consts, + &'a Consts, + &'a FigureModel, + &'a Texture, +); + +/// An entry holding enough information to draw or destroy a figure in a +/// particular cache. +pub struct FigureModelEntry { + /// The estimated bounds of this figure, in voxels. This may not be very + /// useful yet. + _bounds: math::Aabb, + /// Hypothetical texture atlas allocation data for the current figure. + /// Will be useful if we decide to use a packed texture atlas for figures + /// like we do for terrain. + allocation: guillotiere::Allocation, + /// Texture used to store color/light information for this figure entry. + /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different + * LOD levels. */ + col_lights: Texture, + /// Models stored in this figure entry; there may be several for one figure, + /// because of LOD models. + pub models: [FigureModel; N], +} + struct FigureMgrStates { character_states: HashMap>, quadruped_small_states: HashMap>, @@ -1018,7 +1046,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1119,7 +1147,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1220,7 +1248,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1319,7 +1347,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1415,7 +1443,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1500,7 +1528,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1581,7 +1609,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1663,7 +1691,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1748,7 +1776,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1833,7 +1861,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -1929,7 +1957,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -2011,7 +2039,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, in_frustum, is_player, @@ -2043,7 +2071,7 @@ impl FigureMgr { col, dt, state_animation_rate, - &model[0], + &model, lpindex, true, is_player, @@ -2159,14 +2187,7 @@ impl FigureMgr { figure_lod_render_distance, |state| state.visible(), ) { - renderer.render_figure( - model, - &col_lights.col_lights, - global, - locals, - bone_consts, - lod, - ); + renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod); } } } @@ -2215,17 +2236,10 @@ impl FigureMgr { figure_lod_render_distance, |state| state.visible(), ) { - renderer.render_player( - model, - &col_lights.col_lights, - global, - locals, - bone_consts, - lod, - ); + renderer.render_player(model, &col_lights, global, locals, bone_consts, lod); renderer.render_player_shadow( model, - &col_lights.col_lights, + &col_lights, global, bone_consts, lod, @@ -2246,16 +2260,10 @@ impl FigureMgr { body: &Body, loadout: Option<&Loadout>, is_player: bool, - // is_shadow: bool, pos: vek::Vec3, figure_lod_render_distance: f32, filter_state: impl Fn(&FigureStateMeta) -> bool, - ) -> Option<( - &Consts, - &Consts, - &FigureModel, - &FigureColLights, - )> { + ) -> Option { let player_camera_mode = if is_player { camera.get_mode() } else { @@ -2297,7 +2305,7 @@ impl FigureMgr { }, } = self; let col_lights = &mut *col_lights_; - if let Some((locals, bone_consts, model)) = match body { + if let Some((locals, bone_consts, model_entry)) = match body { Body::Humanoid(_) => character_states .get(&entity) .filter(|state| filter_state(&*state)) @@ -2563,14 +2571,14 @@ impl FigureMgr { let figure_mid_detail_distance = figure_lod_render_distance * 0.5; let model = if pos.distance_squared(cam_pos) > figure_low_detail_distance.powf(2.0) { - &model[2] + &model_entry.models[2] } else if pos.distance_squared(cam_pos) > figure_mid_detail_distance.powf(2.0) { - &model[1] + &model_entry.models[1] } else { - &model[0] + &model_entry.models[0] }; - Some((locals, bone_consts, model, &*col_lights_)) + Some((locals, bone_consts, model, col_lights_.texture(model_entry))) } else { // trace!("Body has no saved figure"); None @@ -2584,24 +2592,39 @@ impl FigureMgr { pub struct FigureColLights { atlas: AtlasAllocator, - col_lights: Texture, + // col_lights: Texture, } impl FigureColLights { pub fn new(renderer: &mut Renderer) -> Self { - let (atlas, col_lights) = - Self::make_atlas(renderer).expect("Failed to create texture atlas for figures"); - Self { atlas, col_lights } + let atlas = Self::make_atlas(renderer).expect("Failed to create texture atlas for figures"); + Self { + atlas, /* col_lights, */ + } } - pub fn texture(&self) -> &Texture { &self.col_lights } + /// Find the correct texture for this model entry. + pub fn texture<'a, const N: usize>( + &'a self, + model: &'a FigureModelEntry, + ) -> &'a Texture { + /* &self.col_lights */ + &model.col_lights + } - pub fn create_figure<'a>( + /// NOTE: Panics if the opaque model's length does not fit in a u32. + /// This is parto f the function contract. + /// + /// NOTE: Panics if the vertex range bounds are not in range of the opaque + /// model stored in the BoneMeshes parameter. This is part of the + /// function contract. + pub fn create_figure<'a, const N: usize>( &mut self, renderer: &mut Renderer, greedy: GreedyMesh<'a>, - (opaque, bounds): BoneMeshes, - ) -> Result { + (opaque, bounds): (Mesh, math::Aabb), + vertex_range: [Range; N], + ) -> Result, RenderError> { let (tex, tex_size) = greedy.finalize(); let atlas = &mut self.atlas; let allocation = atlas @@ -2609,21 +2632,32 @@ impl FigureColLights { i32::from(tex_size.x), i32::from(tex_size.y), )) - .expect("Not yet implemented: allocate new atlas on allocation faillure."); + .expect("Not yet implemented: allocate new atlas on allocation failure."); let col_lights = ShadowPipeline::create_col_lights(renderer, (tex, tex_size))?; + let model_len = u32::try_from(opaque.vertices().len()) + .expect("The model size for this figure does not fit in a u32!"); + let model = renderer.create_model(&opaque)?; - Ok(FigureModel { - bounds, - opaque: renderer.create_model(&opaque)?, - // shadow: renderer.create_model(&shadow)?, + Ok(FigureModelEntry { + _bounds: bounds, + models: vertex_range.map(|range| { + assert!( + range.start <= range.end && range.end <= model_len, + "The provided vertex range for figure mesh {:?} does not fit in the model, \ + which is of size {:?}!", + range, + model_len + ); + FigureModel { + opaque: model.submodel(range), + } + }), col_lights, allocation, }) } - fn make_atlas( - renderer: &mut Renderer, - ) -> Result<(AtlasAllocator, Texture), RenderError> { + fn make_atlas(renderer: &mut Renderer) -> Result { let max_texture_size = renderer.max_texture_size(); let atlas_size = guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); @@ -2633,7 +2667,11 @@ impl FigureColLights { large_size_threshold: 256, ..guillotiere::AllocatorOptions::default() }); - let texture = renderer.create_texture_raw( + // TODO: Consider using a single texture atlas to store all figures, much like + // we do for terrain chunks. We previoosly avoided this due to + // perceived performance degradation for the figure use case, but with a + // smaller atlas size this may be less likely. + /* let texture = renderer.create_texture_raw( gfx::texture::Kind::D2( max_texture_size, max_texture_size, @@ -2649,7 +2687,8 @@ impl FigureColLights { gfx::texture::WrapMode::Clamp, ), )?; - Ok((atlas, texture)) + Ok((atlas, texture)) */ + Ok(atlas) } } @@ -2714,7 +2753,7 @@ impl FigureState { } #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 - pub fn update( + pub fn update( &mut self, renderer: &mut Renderer, pos: anim::vek::Vec3, @@ -2723,7 +2762,7 @@ impl FigureState { col: vek::Rgba, dt: f32, state_animation_rate: f32, - model: &FigureModel, + model: &FigureModelEntry, _lpindex: u8, _visible: bool, is_player: bool, @@ -2736,8 +2775,8 @@ impl FigureState { // largest dimension (if we were exact, it should just be half the largest // dimension, but we're not, so we double it and use size() instead of // half_size()). - let radius = model.bounds.half_size().reduce_partial_max(); - let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); + /* let radius = vek::Extent3::::from(model.bounds.half_size()).reduce_partial_max(); + let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); */ self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt); diff --git a/voxygen/src/scene/lod.rs b/voxygen/src/scene/lod.rs index 9dc1092b99..2a40a34021 100644 --- a/voxygen/src/scene/lod.rs +++ b/voxygen/src/scene/lod.rs @@ -82,10 +82,10 @@ fn create_lod_terrain_mesh(detail: u32) -> Mesh { let transform = |x| (2.0 * x as f32) / detail as f32 - 1.0; Quad::new( - Vertex::new(Vec2::new(x + 0, y + 0).map(transform)), - Vertex::new(Vec2::new(x + 1, y + 0).map(transform)), + Vertex::new(Vec2::new(x, y).map(transform)), + Vertex::new(Vec2::new(x + 1, y).map(transform)), Vertex::new(Vec2::new(x + 1, y + 1).map(transform)), - Vertex::new(Vec2::new(x + 0, y + 1).map(transform)), + Vertex::new(Vec2::new(x, y + 1).map(transform)), ) .rotated_by(if (x > detail as i32 / 2) ^ (y > detail as i32 / 2) { 0 diff --git a/voxygen/src/scene/mod.rs b/voxygen/src/scene/mod.rs index e164c53fa5..d465ea942b 100644 --- a/voxygen/src/scene/mod.rs +++ b/voxygen/src/scene/mod.rs @@ -994,7 +994,7 @@ impl Scene { let camera_data = (&self.camera, scene_data.figure_lod_render_distance); // would instead have this as an extension. - if renderer.render_mode().shadow.is_map() && (is_daylight || light_data.1.len() > 0) { + if renderer.render_mode().shadow.is_map() && (is_daylight || !light_data.1.is_empty()) { if is_daylight { // Set up shadow mapping. renderer.start_shadows(); diff --git a/voxygen/src/scene/particle.rs b/voxygen/src/scene/particle.rs index 92e79fc9f7..03a973a004 100644 --- a/voxygen/src/scene/particle.rs +++ b/voxygen/src/scene/particle.rs @@ -53,8 +53,9 @@ impl ParticleMgr { power, reagent, } => { - for _ in 0..150 { - self.particles.push(Particle::new( + self.particles.resize( + self.particles.len() + 150, + Particle::new( Duration::from_millis(if reagent.is_some() { 1000 } else { 250 }), time, match reagent { @@ -66,17 +67,18 @@ impl ParticleMgr { None => ParticleMode::Shrapnel, }, *pos, - )); - } + ), + ); - for _ in 0..200 { - self.particles.push(Particle::new( + self.particles.resize( + self.particles.len() + 200, + Particle::new( Duration::from_secs(4), time, ParticleMode::CampfireSmoke, *pos + Vec2::::zero().map(|_| rng.gen_range(-1.0, 1.0) * power), - )); - } + ), + ); }, Outcome::ProjectileShot { .. } => {}, } @@ -107,14 +109,7 @@ impl ParticleMgr { fn maintain_body_particles(&mut self, scene_data: &SceneData) { let ecs = scene_data.state.ecs(); - for (_i, (_entity, body, pos)) in ( - &ecs.entities(), - &ecs.read_storage::(), - &ecs.read_storage::(), - ) - .join() - .enumerate() - { + for (body, pos) in (&ecs.read_storage::(), &ecs.read_storage::()).join() { match body { Body::Object(object::Body::CampfireLit) => { self.maintain_campfirelit_particles(scene_data, pos) @@ -181,24 +176,26 @@ impl ParticleMgr { let time = scene_data.state.get_time(); // fire - for _ in 0..self.scheduler.heartbeats(Duration::from_millis(3)) { - self.particles.push(Particle::new( + self.particles.resize( + self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(3))), + Particle::new( Duration::from_millis(250), time, ParticleMode::CampfireFire, pos.0, - )); - } + ), + ); // smoke - for _ in 0..self.scheduler.heartbeats(Duration::from_millis(5)) { - self.particles.push(Particle::new( + self.particles.resize( + self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(5))), + Particle::new( Duration::from_secs(2), time, ParticleMode::CampfireSmoke, pos.0, - )); - } + ), + ); } fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos) { @@ -228,23 +225,23 @@ impl ParticleMgr { let ecs = state.ecs(); let time = state.get_time(); - for (_i, (_entity, pos, character_state)) in ( - &ecs.entities(), + for (pos, character_state) in ( &ecs.read_storage::(), &ecs.read_storage::(), ) .join() - .enumerate() { if let CharacterState::Boost(_) = character_state { - for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) { - self.particles.push(Particle::new( + self.particles.resize( + self.particles.len() + + usize::from(self.scheduler.heartbeats(Duration::from_millis(10))), + Particle::new( Duration::from_secs(15), time, ParticleMode::CampfireSmoke, pos.0, - )); - } + ), + ); } } } @@ -393,6 +390,7 @@ impl HeartbeatScheduler { pub fn clear(&mut self) { self.timers.clear() } } +#[derive(Clone, Copy)] struct Particle { alive_until: f64, // created_at + lifespan instance: ParticleInstance, diff --git a/voxygen/src/scene/simple.rs b/voxygen/src/scene/simple.rs index d33426e6b0..8771fac83b 100644 --- a/voxygen/src/scene/simple.rs +++ b/voxygen/src/scene/simple.rs @@ -2,12 +2,12 @@ use crate::{ mesh::{greedy::GreedyMesh, Meshable}, render::{ create_pp_mesh, create_skybox_mesh, BoneMeshes, Consts, FigureModel, FigurePipeline, - GlobalModel, Globals, Light, Model, PostProcessLocals, PostProcessPipeline, Renderer, - Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline, + GlobalModel, Globals, Light, Mesh, Model, PostProcessLocals, PostProcessPipeline, Renderer, + Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline, TerrainPipeline, }, scene::{ camera::{self, Camera, CameraMode}, - figure::{load_mesh, FigureColLights, FigureModelCache, FigureState}, + figure::{load_mesh, FigureColLights, FigureModelCache, FigureModelEntry, FigureState}, LodData, }, window::{Event, PressState}, @@ -46,13 +46,14 @@ impl ReadVol for VoidVol { fn generate_mesh<'a>( greedy: &mut GreedyMesh<'a>, + mesh: &mut Mesh, segment: Segment, offset: Vec3, ) -> BoneMeshes { let (opaque, _, /* shadow */ _, bounds) = Meshable::::generate_mesh( segment, - (greedy, offset, Vec3::one()), + (greedy, mesh, offset, Vec3::one()), ); (opaque /* , shadow */, bounds) } @@ -77,7 +78,7 @@ pub struct Scene { map_bounds: Vec2, col_lights: FigureColLights, - backdrop: Option<(FigureModel, FigureState)>, + backdrop: Option<(FigureModelEntry<1>, FigureState)>, figure_model_cache: FigureModelCache, figure_state: FigureState, @@ -150,12 +151,20 @@ impl Scene { backdrop: backdrop.map(|specifier| { let mut state = FigureState::new(renderer, FixtureSkeleton::default()); let mut greedy = FigureModel::make_greedy(); - let mesh = load_mesh( + let mut opaque_mesh = Mesh::new(); + let (_opaque_mesh, (bounds, range)) = load_mesh( specifier, Vec3::new(-55.0, -49.5, -2.0), - |segment, offset| generate_mesh(&mut greedy, segment, offset), + |segment, offset| generate_mesh(&mut greedy, &mut opaque_mesh, segment, offset), ); - let model = col_lights.create_figure(renderer, greedy, mesh).unwrap(); + // NOTE: Since MagicaVoxel sizes are limited to 256 × 256 × 256, and there are + // at most 3 meshed vertices per unique vertex, we know the + // total size is bounded by 2^24 * 3 * 1.5 which is bounded by + // 2^27, which fits in a u32. + let range = range.start as u32..range.end as u32; + let model = col_lights + .create_figure(renderer, greedy, (opaque_mesh, bounds), [range]) + .unwrap(); let mut buf = [Default::default(); anim::MAX_BONE_COUNT]; state.update( renderer, @@ -315,7 +324,7 @@ impl Scene { Rgba::broadcast(1.0), scene_data.delta_time, 1.0, - &model[0], + &model, 0, true, false, @@ -354,8 +363,8 @@ impl Scene { .0; renderer.render_figure( - &model[0], - &self.col_lights.texture(), + &model.models[0], + &self.col_lights.texture(model), &self.data, self.figure_state.locals(), self.figure_state.bone_consts(), @@ -365,8 +374,8 @@ impl Scene { if let Some((model, state)) = &self.backdrop { renderer.render_figure( - model, - &self.col_lights.texture(), + &model.models[0], + &self.col_lights.texture(model), &self.data, state.locals(), state.bone_consts(), diff --git a/voxygen/src/scene/terrain.rs b/voxygen/src/scene/terrain.rs index 6d189155dc..06ec276b93 100644 --- a/voxygen/src/scene/terrain.rs +++ b/voxygen/src/scene/terrain.rs @@ -393,7 +393,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( volume: as SampleVol>>::Sample, max_texture_size: u16, range: Aabb, - sprite_models: &HashMap<(BlockKind, usize), Vec>, + sprite_data: &HashMap<(BlockKind, usize), Vec>, ) -> MeshWorkerResponse { let (opaque_mesh, fluid_mesh, _shadow_mesh, (bounds, col_lights_info)) = volume.generate_mesh((range, Vec2::new(max_texture_size, max_texture_size))); @@ -424,7 +424,7 @@ fn mesh_worker + RectRasterableVol + ReadVol + Debug>( let key = (block.kind(), variation); // NOTE: Safe bbecause we called sprite_config_for already. // NOTE: Safe because 0 ≤ ori < 8 - let sprite_data = &sprite_models[&key][0]; + let sprite_data = &sprite_data[&key][0]; let instance = SpriteInstance::new( Mat4::identity() .translated_3d(sprite_data.offset) @@ -484,7 +484,7 @@ pub struct Terrain { mesh_todo: HashMap, ChunkMeshState>, // GPU data - sprite_models: Arc>>, + sprite_data: Arc>>, sprite_col_lights: Texture, col_lights: Texture, waves: Texture, @@ -513,6 +513,7 @@ impl Terrain { guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); let mut greedy = GreedyMesh::new(max_size); let mut locals_buffer = [SpriteLocals::default(); 8]; + // NOTE: Tracks the start vertex of the next model to be meshed. let mut make_models = |(kind, variation), s, offset, lod_axes: Vec3| { let scaled = [1.0, 0.8, 0.6, 0.4, 0.2]; let model = assets::load_expect::(s); @@ -550,12 +551,21 @@ impl Terrain { lod_axes * lod_scale_orig + lod_axes.map(|e| if e == 0.0 { 1.0 } else { 0.0 }) }; - let opaque_model = - Meshable::::generate_mesh( - Segment::from(model.as_ref()).scaled_by(lod_scale), - (&mut greedy, wind_sway >= 0.4 && lod_scale_orig == 1.0), - ) - .0; + // Mesh generation exclusively acts using side effects; it has no + // interesting return value, but updates the mesh. + let mut opaque_mesh = Mesh::new(); + Meshable::::generate_mesh( + Segment::from(model.as_ref()).scaled_by(lod_scale), + ( + &mut greedy, + &mut opaque_mesh, + wind_sway >= 0.4 && lod_scale_orig == 1.0, + ), + ); + let model = renderer + .create_model(&opaque_mesh) + .expect("Failed to upload sprite model data to the GPU!"); + let sprite_scale = Vec3::one() / lod_scale; let sprite_mat: Mat4 = sprite_mat * Mat4::scaling_3d(sprite_scale); locals_buffer @@ -569,8 +579,8 @@ impl Terrain { }); SpriteData { + /* vertex_range */ model, offset, - model: renderer.create_model(&opaque_model).unwrap(), locals: renderer .create_consts(&locals_buffer) .expect("Failed to upload sprite locals to the GPU!"), @@ -580,7 +590,7 @@ impl Terrain { ) }; - let sprite_models: HashMap<(BlockKind, usize), _> = vec![ + let sprite_data: HashMap<(BlockKind, usize), _> = vec![ // Windows make_models( (BlockKind::Window1, 0), @@ -2370,6 +2380,7 @@ impl Terrain { .collect(); let sprite_col_lights = ShadowPipeline::create_col_lights(renderer, greedy.finalize()) .expect("Failed to upload sprite color and light data to the GPU!"); + Self { atlas, chunks: HashMap::default(), @@ -2377,7 +2388,7 @@ impl Terrain { mesh_send_tmp: send, mesh_recv: recv, mesh_todo: HashMap::default(), - sprite_models: Arc::new(sprite_models), + sprite_data: Arc::new(sprite_data), sprite_col_lights, waves: renderer .create_texture( @@ -2624,9 +2635,9 @@ impl Terrain { // Queue the worker thread. let started_tick = todo.started_tick; - let sprite_models = Arc::clone(&self.sprite_models); + let sprite_data = Arc::clone(&self.sprite_data); scene_data.thread_pool.execute(move || { - let sprite_models = sprite_models; + let sprite_data = sprite_data; let _ = send.send(mesh_worker( pos, (min_z as f32, max_z as f32), @@ -2634,7 +2645,7 @@ impl Terrain { volume, max_texture_size, aabb, - &sprite_models, + &sprite_data, )); }); todo.active_worker = Some(todo.started_tick); @@ -3067,15 +3078,15 @@ impl Terrain { && dist_sqrd <= chunk_mag || dist_sqrd < sprite_high_detail_distance.powf(2.0) { - &self.sprite_models[&kind][0] + &self.sprite_data[&kind][0] } else if dist_sqrd < sprite_hid_detail_distance.powf(2.0) { - &self.sprite_models[&kind][1] + &self.sprite_data[&kind][1] } else if dist_sqrd < sprite_mid_detail_distance.powf(2.0) { - &self.sprite_models[&kind][2] + &self.sprite_data[&kind][2] } else if dist_sqrd < sprite_low_detail_distance.powf(2.0) { - &self.sprite_models[&kind][3] + &self.sprite_data[&kind][3] } else { - &self.sprite_models[&kind][4] + &self.sprite_data[&kind][4] }; renderer.render_sprites( model, diff --git a/voxygen/src/settings.rs b/voxygen/src/settings.rs index 9af7fbb012..bca5eec04e 100644 --- a/voxygen/src/settings.rs +++ b/voxygen/src/settings.rs @@ -659,12 +659,7 @@ pub enum AudioOutput { } impl AudioOutput { - pub fn is_enabled(&self) -> bool { - match self { - Self::Off => false, - _ => true, - } - } + pub fn is_enabled(&self) -> bool { !matches!(self, Self::Off) } } /// `AudioSettings` controls the volume of different audio subsystems and which /// device is used. diff --git a/voxygen/src/ui/event.rs b/voxygen/src/ui/event.rs index e5a30b62cf..e84deca87f 100644 --- a/voxygen/src/ui/event.rs +++ b/voxygen/src/ui/event.rs @@ -30,23 +30,19 @@ impl Event { } pub fn is_keyboard_or_mouse(&self) -> bool { - match self.0 { + matches!(self.0, Input::Press(_) | Input::Release(_) | Input::Motion(_) | Input::Touch(_) - | Input::Text(_) => true, - _ => false, - } + | Input::Text(_)) } pub fn is_keyboard(&self) -> bool { - match self.0 { + matches!(self.0, Input::Press(Button::Keyboard(_)) | Input::Release(Button::Keyboard(_)) - | Input::Text(_) => true, - _ => false, - } + | Input::Text(_)) } pub fn new_resize(dims: Vec2) -> Self { Self(Input::Resize(dims.x, dims.y)) } diff --git a/voxygen/src/ui/mod.rs b/voxygen/src/ui/mod.rs index 182f1e4b87..92743c625a 100644 --- a/voxygen/src/ui/mod.rs +++ b/voxygen/src/ui/mod.rs @@ -43,13 +43,12 @@ use conrod_core::{ widget::{self, id::Generator}, Rect, Scalar, UiBuilder, UiCell, }; +use core::{convert::TryInto, f32, f64, ops::Range}; use graphic::{Rotation, TexId}; use hashbrown::hash_map::Entry; use std::{ - f32, f64, fs::File, io::{BufReader, Read}, - ops::Range, sync::Arc, time::Duration, }; @@ -67,7 +66,7 @@ enum DrawKind { Plain, } enum DrawCommand { - Draw { kind: DrawKind, verts: Range }, + Draw { kind: DrawKind, verts: Range }, Scissor(Aabr), WorldPos(Option), } @@ -75,14 +74,28 @@ impl DrawCommand { fn image(verts: Range, id: TexId) -> DrawCommand { DrawCommand::Draw { kind: DrawKind::Image(id), - verts, + verts: verts + .start + .try_into() + .expect("Vertex count for UI rendering does not fit in a u32!") + ..verts + .end + .try_into() + .expect("Vertex count for UI rendering does not fit in a u32!"), } } fn plain(verts: Range) -> DrawCommand { DrawCommand::Draw { kind: DrawKind::Plain, - verts, + verts: verts + .start + .try_into() + .expect("Vertex count for UI rendering does not fit in a u32!") + ..verts + .end + .try_into() + .expect("Vertex count for UI rendering does not fit in a u32!"), } } } @@ -981,7 +994,7 @@ impl Ui { DrawKind::Plain => self.cache.glyph_cache_tex(), }; let model = self.model.submodel(verts.clone()); - renderer.render_ui_element(&model, tex, scissor, globals, locals); + renderer.render_ui_element(model, tex, scissor, globals, locals); }, } } diff --git a/world/src/lib.rs b/world/src/lib.rs index 2b1f21d8dd..55a5f12331 100644 --- a/world/src/lib.rs +++ b/world/src/lib.rs @@ -3,7 +3,6 @@ #![allow(clippy::option_map_unit_fn)] #![feature( arbitrary_enum_discriminant, - const_if_match, const_generics, const_panic, label_break_value, diff --git a/world/src/sim/erosion.rs b/world/src/sim/erosion.rs index 32c24812c1..e5128c60b5 100644 --- a/world/src/sim/erosion.rs +++ b/world/src/sim/erosion.rs @@ -120,29 +120,11 @@ pub enum RiverKind { } impl RiverKind { - pub fn is_ocean(&self) -> bool { - if let RiverKind::Ocean = *self { - true - } else { - false - } - } + pub fn is_ocean(&self) -> bool { matches!(*self, RiverKind::Ocean) } - pub fn is_river(&self) -> bool { - if let RiverKind::River { .. } = *self { - true - } else { - false - } - } + pub fn is_river(&self) -> bool { matches!(*self, RiverKind::River { .. }) } - pub fn is_lake(&self) -> bool { - if let RiverKind::Lake { .. } = *self { - true - } else { - false - } - } + pub fn is_lake(&self) -> bool { matches!(*self, RiverKind::Lake { .. }) } } impl PartialOrd for RiverKind { diff --git a/world/src/sim/map.rs b/world/src/sim/map.rs index 12d593f502..1d3dd88831 100644 --- a/world/src/sim/map.rs +++ b/world/src/sim/map.rs @@ -170,9 +170,7 @@ pub fn sample_pos( } }); - let downhill_wpos = downhill - .map(|downhill_pos| downhill_pos) - .unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32)); + let downhill_wpos = downhill.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32)); let alt = if is_basement { basement } else { alt }; let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64; diff --git a/world/src/site/settlement/building/archetype/house.rs b/world/src/site/settlement/building/archetype/house.rs index fc0f3365be..970e3a4698 100644 --- a/world/src/site/settlement/building/archetype/house.rs +++ b/world/src/site/settlement/building/archetype/house.rs @@ -93,21 +93,9 @@ pub enum StoreyFill { } impl StoreyFill { - fn has_lower(&self) -> bool { - if let StoreyFill::All = self { - true - } else { - false - } - } + fn has_lower(&self) -> bool { matches!(self, StoreyFill::All) } - fn has_upper(&self) -> bool { - if let StoreyFill::None = self { - false - } else { - true - } - } + fn has_upper(&self) -> bool { !matches!(self, StoreyFill::None) } } #[derive(Copy, Clone)] diff --git a/world/src/site/settlement/mod.rs b/world/src/site/settlement/mod.rs index 7b9f418965..4ead8da0bc 100644 --- a/world/src/site/settlement/mod.rs +++ b/world/src/site/settlement/mod.rs @@ -236,10 +236,7 @@ impl Settlement { let origin = dir.map(|e| (e * 100.0) as i32); let origin = self .land - .find_tile_near(origin, |plot| match plot { - Some(&Plot::Field { .. }) => true, - _ => false, - }) + .find_tile_near(origin, |plot| matches!(plot, Some(&Plot::Field { .. }))) .unwrap(); if let Some(path) = self.town.as_ref().and_then(|town| { @@ -520,7 +517,7 @@ impl Settlement { .land .get_at_block(wpos - self.origin) .plot - .map(|p| if let Plot::Hazard = p { true } else { false }) + .map(|p| matches!(p, Plot::Hazard)) .unwrap_or(true), ..SpawnRules::default() }