Merge more models into one mesh than we did previously.

This commit is contained in:
Joshua Yanovski 2020-08-15 21:15:02 +02:00
parent 3155c31e66
commit 10245e0c1b
51 changed files with 559 additions and 583 deletions

View File

@ -43,10 +43,7 @@ impl Alignment {
// TODO: Remove this hack // TODO: Remove this hack
pub fn is_friendly_to_players(&self) -> bool { pub fn is_friendly_to_players(&self) -> bool {
match self { matches!(self, Alignment::Npc | Alignment::Tame | Alignment::Owned(_))
Alignment::Npc | Alignment::Tame | Alignment::Owned(_) => true,
_ => false,
}
} }
} }
@ -129,19 +126,9 @@ pub enum Activity {
} }
impl Activity { impl Activity {
pub fn is_follow(&self) -> bool { pub fn is_follow(&self) -> bool { matches!(self, Activity::Follow { .. }) }
match self {
Activity::Follow { .. } => true,
_ => false,
}
}
pub fn is_attack(&self) -> bool { pub fn is_attack(&self) -> bool { matches!(self, Activity::Attack { .. }) }
match self {
Activity::Attack { .. } => true,
_ => false,
}
}
} }
impl Default for Activity { impl Default for Activity {

View File

@ -201,12 +201,7 @@ impl<
} }
impl Body { impl Body {
pub fn is_humanoid(&self) -> bool { pub fn is_humanoid(&self) -> bool { matches!(self, Body::Humanoid(_)) }
match self {
Body::Humanoid(_) => true,
_ => false,
}
}
// Note: this might need to be refined to something more complex for realistic // Note: this might need to be refined to something more complex for realistic
// behavior with less cylindrical bodies (e.g. wolfs) // behavior with less cylindrical bodies (e.g. wolfs)

View File

@ -73,7 +73,7 @@ pub enum CharacterState {
impl CharacterState { impl CharacterState {
pub fn is_wield(&self) -> bool { pub fn is_wield(&self) -> bool {
match self { matches!(self,
CharacterState::Wielding CharacterState::Wielding
| CharacterState::BasicMelee(_) | CharacterState::BasicMelee(_)
| CharacterState::BasicRanged(_) | CharacterState::BasicRanged(_)
@ -82,50 +82,37 @@ impl CharacterState {
| CharacterState::BasicBlock | CharacterState::BasicBlock
| CharacterState::LeapMelee(_) | CharacterState::LeapMelee(_)
| CharacterState::SpinMelee(_) | CharacterState::SpinMelee(_)
| CharacterState::ChargedRanged(_) => true, | CharacterState::ChargedRanged(_)
_ => false, )
}
} }
pub fn is_attack(&self) -> bool { pub fn is_attack(&self) -> bool {
match self { matches!(self,
CharacterState::BasicMelee(_) CharacterState::BasicMelee(_)
| CharacterState::BasicRanged(_) | CharacterState::BasicRanged(_)
| CharacterState::DashMelee(_) | CharacterState::DashMelee(_)
| CharacterState::TripleStrike(_) | CharacterState::TripleStrike(_)
| CharacterState::LeapMelee(_) | CharacterState::LeapMelee(_)
| CharacterState::SpinMelee(_) | CharacterState::SpinMelee(_)
| CharacterState::ChargedRanged(_) => true, | CharacterState::ChargedRanged(_)
_ => false, )
}
} }
pub fn is_aimed(&self) -> bool { pub fn is_aimed(&self) -> bool {
match self { matches!(self,
CharacterState::BasicMelee(_) CharacterState::BasicMelee(_)
| CharacterState::BasicRanged(_) | CharacterState::BasicRanged(_)
| CharacterState::DashMelee(_) | CharacterState::DashMelee(_)
| CharacterState::TripleStrike(_) | CharacterState::TripleStrike(_)
| CharacterState::BasicBlock | CharacterState::BasicBlock
| CharacterState::LeapMelee(_) | CharacterState::LeapMelee(_)
| CharacterState::ChargedRanged(_) => true, | CharacterState::ChargedRanged(_)
_ => false, )
}
} }
pub fn is_block(&self) -> bool { pub fn is_block(&self) -> bool { matches!(self, CharacterState::BasicBlock) }
match self {
CharacterState::BasicBlock => true,
_ => false,
}
}
pub fn is_dodge(&self) -> bool { pub fn is_dodge(&self) -> bool { matches!(self, CharacterState::Roll(_)) }
match self {
CharacterState::Roll(_) => true,
_ => false,
}
}
/// Compares for shallow equality (does not check internal struct equality) /// Compares for shallow equality (does not check internal struct equality)
pub fn same_variant(&self, other: &Self) -> bool { pub fn same_variant(&self, other: &Self) -> bool {

View File

@ -25,10 +25,5 @@ pub enum MatCell {
impl Vox for MatCell { impl Vox for MatCell {
fn empty() -> Self { MatCell::None } fn empty() -> Self { MatCell::None }
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool { matches!(self, MatCell::None) }
match self {
MatCell::None => true,
_ => false,
}
}
} }

View File

@ -4,7 +4,6 @@
#![feature( #![feature(
arbitrary_enum_discriminant, arbitrary_enum_discriminant,
const_checked_int_methods, const_checked_int_methods,
const_if_match,
option_unwrap_none, option_unwrap_none,
bool_to_option, bool_to_option,
label_break_value, label_break_value,

View File

@ -201,12 +201,7 @@ impl BlockKind {
} }
} }
pub fn is_fluid(&self) -> bool { pub fn is_fluid(&self) -> bool { matches!(self, BlockKind::Water) }
match self {
BlockKind::Water => true,
_ => false,
}
}
pub fn get_glow(&self) -> Option<u8> { pub fn get_glow(&self) -> Option<u8> {
// TODO: When we have proper volumetric lighting // TODO: When we have proper volumetric lighting

View File

@ -175,11 +175,7 @@ impl MapSizeLg {
map_size_lg.y + TERRAIN_CHUNK_BLOCKS_LG < 32; map_size_lg.y + TERRAIN_CHUNK_BLOCKS_LG < 32;
// Assertion on dimensions: product of dimensions must fit in a usize. // Assertion on dimensions: product of dimensions must fit in a usize.
let chunks_product_in_range = let chunks_product_in_range =
if let Some(_) = 1usize.checked_shl(map_size_lg.x + map_size_lg.y) { matches!(1usize.checked_shl(map_size_lg.x + map_size_lg.y), Some(_));
true
} else {
false
};
if blocks_in_range && chunks_product_in_range { if blocks_in_range && chunks_product_in_range {
// Cleared all invariants. // Cleared all invariants.
Ok(MapSizeLg(map_size_lg)) Ok(MapSizeLg(map_size_lg))

View File

@ -32,12 +32,7 @@ pub enum StructureBlock {
impl Vox for StructureBlock { impl Vox for StructureBlock {
fn empty() -> Self { StructureBlock::None } fn empty() -> Self { StructureBlock::None }
fn is_empty(&self) -> bool { fn is_empty(&self) -> bool { matches!(self, StructureBlock::None) }
match self {
StructureBlock::None => true,
_ => false,
}
}
} }
#[derive(Debug)] #[derive(Debug)]

View File

@ -1,7 +1,7 @@
#![deny(unsafe_code)] #![deny(unsafe_code)]
#![cfg_attr(test, deny(rust_2018_idioms))] #![cfg_attr(test, deny(rust_2018_idioms))]
#![cfg_attr(test, deny(warnings))] #![cfg_attr(test, deny(warnings))]
#![feature(try_trait, const_if_match)] #![feature(try_trait)]
//! Crate to handle high level networking of messages with different //! Crate to handle high level networking of messages with different
//! requirements and priorities over a number of protocols //! requirements and priorities over a number of protocols

View File

@ -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 { pub(crate) fn partial_eq_bincode(first: &bincode::ErrorKind, second: &bincode::ErrorKind) -> bool {
use bincode::ErrorKind::*;
match *first { match *first {
bincode::ErrorKind::Io(ref f) => match *second { Io(ref f) => matches!(*second, Io(ref s) if partial_eq_io_error(f, s)),
bincode::ErrorKind::Io(ref s) => partial_eq_io_error(f, s), InvalidUtf8Encoding(f) => matches!(*second, InvalidUtf8Encoding(s) if f == s),
_ => false, InvalidBoolEncoding(f) => matches!(*second, InvalidBoolEncoding(s) if f == s),
}, InvalidCharEncoding => matches!(*second, InvalidCharEncoding),
bincode::ErrorKind::InvalidUtf8Encoding(f) => match *second { InvalidTagEncoding(f) => matches!(*second, InvalidTagEncoding(s) if f == s),
bincode::ErrorKind::InvalidUtf8Encoding(s) => f == s, DeserializeAnyNotSupported => matches!(*second, DeserializeAnyNotSupported),
_ => false, SizeLimit => matches!(*second, SizeLimit),
}, SequenceMustHaveLength => matches!(*second, SequenceMustHaveLength),
bincode::ErrorKind::InvalidBoolEncoding(f) => match *second { Custom(ref f) => matches!(*second, Custom(ref s) if f == s),
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,
},
} }
} }

View File

@ -1 +1 @@
nightly-2020-06-22 nightly-2020-08-14

View File

@ -50,17 +50,17 @@ impl Client {
} }
pub fn is_registered(&self) -> bool { pub fn is_registered(&self) -> bool {
match self.client_state { matches!(
ClientState::Registered | ClientState::Spectator | ClientState::Character => true, self.client_state,
_ => false, ClientState::Registered | ClientState::Spectator | ClientState::Character
} )
} }
pub fn is_ingame(&self) -> bool { pub fn is_ingame(&self) -> bool {
match self.client_state { matches!(
ClientState::Spectator | ClientState::Character => true, self.client_state,
_ => false, ClientState::Spectator | ClientState::Character
} )
} }
pub fn allow_state(&mut self, new_state: ClientState) { pub fn allow_state(&mut self, new_state: ClientState) {

View File

@ -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 /// 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 /// the kill. Experience given is equal to the level of the entity that was
/// killed times 10. /// 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) { pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSource) {
let state = server.state_mut(); let state = server.state_mut();

View File

@ -33,6 +33,7 @@ pub fn snuff_lantern(storage: &mut WriteStorage<comp::LightEmitter>, entity: Ecs
} }
#[allow(clippy::blocks_in_if_conditions)] #[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) { pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) {
let state = server.state_mut(); let state = server.state_mut();
let mut dropped_items = Vec::new(); 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)), .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)) = if let (Some(item), Some(pos)) =
(item, state.ecs().read_storage::<comp::Pos>().get(entity)) (item, state.ecs().read_storage::<comp::Pos>().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 recipe_book = default_recipe_book();
let craft_result = recipe_book.get(&recipe).and_then(|r| r.perform(inv).ok()); 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() { if craft_result.is_some() {
let _ = state.ecs().write_storage().insert( let _ = state.ecs().write_storage().insert(
entity, entity,

View File

@ -214,7 +214,6 @@ impl StateExt for State {
// Notify clients of a player list update // Notify clients of a player list update
let client_uid = self let client_uid = self
.read_component_cloned::<Uid>(entity) .read_component_cloned::<Uid>(entity)
.map(|u| u)
.expect("Client doesn't have a Uid!!!"); .expect("Client doesn't have a Uid!!!");
self.notify_registered_clients(ServerMsg::PlayerListUpdate( self.notify_registered_clients(ServerMsg::PlayerListUpdate(

View File

@ -338,7 +338,7 @@ impl<'a> System<'a> for Sys {
.filter(|o| o.get_pos().and_then(&is_near).unwrap_or(true)) .filter(|o| o.get_pos().and_then(&is_near).unwrap_or(true))
.cloned() .cloned()
.collect::<Vec<_>>(); .collect::<Vec<_>>();
if outcomes.len() > 0 { if !outcomes.is_empty() {
client.notify(ServerMsg::Outcomes(outcomes)); client.notify(ServerMsg::Outcomes(outcomes));
} }
} }

View File

@ -192,7 +192,7 @@ impl Sys {
}); });
// Give the player a welcome message // Give the player a welcome message
if settings.server_description.len() > 0 { if !settings.server_description.is_empty() {
client.notify( client.notify(
ChatType::CommandInfo ChatType::CommandInfo
.server_msg(settings.server_description.clone()), .server_msg(settings.server_description.clone()),

View File

@ -46,19 +46,16 @@ pub struct AudioFrontend {
impl AudioFrontend { impl AudioFrontend {
/// Construct with given device /// Construct with given device
#[allow(clippy::redundant_clone)] // TODO: Pending review in #587
pub fn new(device: String, max_sfx_channels: usize) -> Self { 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 audio_device = get_device_raw(&device);
let mut sfx_channels = Vec::with_capacity(max_sfx_channels);
if let Some(audio_device) = &audio_device { if let Some(audio_device) = &audio_device {
for _ in 0..max_sfx_channels { sfx_channels.resize_with(max_sfx_channels, || SfxChannel::new(&audio_device));
sfx_channels.push(SfxChannel::new(&audio_device));
}
} }
Self { Self {
device: device.clone(), device,
device_list: list_devices(), device_list: list_devices(),
audio_device, audio_device,
sound_cache: SoundCache::default(), sound_cache: SoundCache::default(),

View File

@ -172,11 +172,7 @@ impl CombatEventMapper {
/// ::Equipping to mean the weapon is drawn. This will need updating if the /// ::Equipping to mean the weapon is drawn. This will need updating if the
/// animations change to match the wield_duration associated with the weapon /// animations change to match the wield_duration associated with the weapon
fn weapon_drawn(character: &CharacterState) -> bool { fn weapon_drawn(character: &CharacterState) -> bool {
character.is_wield() character.is_wield() || matches!(character, CharacterState::Equipping { .. })
|| match character {
CharacterState::Equipping { .. } => true,
_ => false,
}
} }
} }

View File

@ -395,10 +395,7 @@ impl<'a> Widget for Chat<'a> {
.widget_input(state.ids.chat_input) .widget_input(state.ids.chat_input)
.presses() .presses()
.key() .key()
.any(|key_press| match key_press.key { .any(|key_press| matches!(key_press.key, Key::Return if !state.input.is_empty()))
Key::Return if !state.input.is_empty() => true,
_ => false,
})
{ {
let msg = state.input.clone(); let msg = state.input.clone();
state.update(|s| { state.update(|s| {

View File

@ -482,10 +482,7 @@ impl Show {
|| self.spell || self.spell
|| self.help || self.help
|| self.intro || self.intro
|| match self.open_windows { || !matches!(self.open_windows, Windows::None)
Windows::None => false,
_ => true,
}
{ {
self.bag = false; self.bag = false;
self.esc_menu = false; self.esc_menu = false;
@ -2470,18 +2467,15 @@ impl Hud {
// conrod eats tabs. Un-eat a tabstop so tab completion can work // conrod eats tabs. Un-eat a tabstop so tab completion can work
if self.ui.ui.global_input().events().any(|event| { if self.ui.ui.global_input().events().any(|event| {
use conrod_core::{event, input}; use conrod_core::{event, input};
match event { matches!(event,
//event::Event::Raw(event::Input::Press(input::Button::Keyboard(input::Key::Tab))) /* event::Event::Raw(event::Input::Press(input::Button::Keyboard(input::Key::Tab))) | */
// => true,
event::Event::Ui(event::Ui::Press( event::Event::Ui(event::Ui::Press(
_, _,
event::Press { event::Press {
button: event::Button::Keyboard(input::Key::Tab), button: event::Button::Keyboard(input::Key::Tab),
.. ..
}, },
)) => true, )))
_ => false,
}
}) { }) {
self.ui self.ui
.ui .ui

View File

@ -1,6 +1,6 @@
#![deny(unsafe_code)] #![deny(unsafe_code)]
#![allow(clippy::option_map_unit_fn)] #![allow(clippy::option_map_unit_fn, incomplete_features)]
#![feature(drain_filter, bool_to_option, or_patterns)] #![feature(array_map, bool_to_option, const_generics, drain_filter, or_patterns)]
#![recursion_limit = "2048"] #![recursion_limit = "2048"]
#[macro_use] #[macro_use]

View File

@ -161,22 +161,20 @@ impl<'a> GreedyMesh<'a> {
pub fn push<M: PartialEq, D: 'a, FL, FC, FO, FS, FP>( pub fn push<M: PartialEq, D: 'a, FL, FC, FO, FS, FP>(
&mut self, &mut self,
config: GreedyConfig<D, FL, FC, FO, FS, FP>, config: GreedyConfig<D, FL, FC, FO, FS, FP>,
) -> Aabb<u16> ) where
where
FL: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a, FL: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
FC: for<'r> FnMut(&'r mut D, Vec3<i32>) -> Rgb<u8> + 'a, FC: for<'r> FnMut(&'r mut D, Vec3<i32>) -> Rgb<u8> + 'a,
FO: for<'r> FnMut(&'r mut D, Vec3<i32>) -> bool + 'a, FO: for<'r> FnMut(&'r mut D, Vec3<i32>) -> bool + 'a,
FS: for<'r> FnMut(&'r mut D, Vec3<i32>, Vec3<i32>, Vec2<Vec3<i32>>) -> Option<(bool, M)>, FS: for<'r> FnMut(&'r mut D, Vec3<i32>, Vec3<i32>, Vec2<Vec3<i32>>) -> Option<(bool, M)>,
FP: FnMut(Vec2<u16>, Vec2<Vec2<u16>>, Vec3<f32>, Vec2<Vec3<f32>>, Vec3<f32>, &M), FP: FnMut(Vec2<u16>, Vec2<Vec2<u16>>, Vec3<f32>, Vec2<Vec3<f32>>, Vec3<f32>, &M),
{ {
let (bounds, cont) = greedy_mesh( let cont = greedy_mesh(
&mut self.atlas, &mut self.atlas,
&mut self.col_lights_size, &mut self.col_lights_size,
self.max_size, self.max_size,
config, config,
); );
self.suspended.push(cont); self.suspended.push(cont);
bounds
} }
/// Finalize the mesh, producing texture color data for the whole model. /// 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 should_draw,
mut push_quad, mut push_quad,
}: GreedyConfig<D, FL, FC, FO, FS, FP>, }: GreedyConfig<D, FL, FC, FO, FS, FP>,
) -> (Aabb<u16>, Box<SuspendedMesh<'a>>) ) -> Box<SuspendedMesh<'a>>
where where
FL: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a, FL: for<'r> FnMut(&'r mut D, Vec3<i32>) -> f32 + 'a,
FC: for<'r> FnMut(&'r mut D, Vec3<i32>) -> Rgb<u8> + 'a, FC: for<'r> FnMut(&'r mut D, Vec3<i32>) -> Rgb<u8> + 'a,
@ -365,30 +363,23 @@ where
}, },
); );
let bounds = Aabb { Box::new(move |col_lights_info| {
min: Vec3::zero(), let mut data = data;
// NOTE: Safe because greedy_size fit in u16. draw_col_lights(
max: greedy_size.map(|e| e as u16), col_lights_info,
}; &mut data,
( todo_rects,
bounds, draw_delta,
Box::new(move |col_lights_info| { get_light,
let mut data = data; get_color,
draw_col_lights( get_opacity,
col_lights_info, TerrainVertex::make_col_light,
&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<M: PartialEq>( fn greedy_mesh_cross_section<M: PartialEq>(
dims: Vec3<usize>, dims: Vec3<usize>,
// Should we draw a face here (below this vertex)? If so, provide its meta information. // 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 /// 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 /// generate a texture of minimal size; we now proceed to create and populate
/// it. /// it.
/// // TODO: Consider using the heavier interface (not the simple one) which seems
/// TODO: Consider using the heavier interface (not the simple one) which seems // to provide builtin support for what we're doing here.
/// to provide builtin support for what we're doing here. //
// TODO: See if we can speed this up using SIMD.
fn draw_col_lights<D>( fn draw_col_lights<D>(
(col_lights, cur_size): &mut ColLightInfo, (col_lights, cur_size): &mut ColLightInfo,
data: &mut D, data: &mut D,
@ -610,6 +602,7 @@ fn draw_col_lights<D>(
/// Precondition: when this function is called, atlas_pos should reflect an /// 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). /// 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<M>( fn create_quad_greedy<M>(
origin: Vec3<usize>, origin: Vec3<usize>,
dim: Vec2<usize>, dim: Vec2<usize>,

View File

@ -7,11 +7,13 @@ use crate::{
self, FigurePipeline, Mesh, ParticlePipeline, ShadowPipeline, SpritePipeline, self, FigurePipeline, Mesh, ParticlePipeline, ShadowPipeline, SpritePipeline,
TerrainPipeline, TerrainPipeline,
}, },
scene::math,
}; };
use common::{ use common::{
figure::Cell, figure::Cell,
vol::{BaseVol, ReadVol, SizedVol, Vox}, vol::{BaseVol, ReadVol, SizedVol, Vox},
}; };
use core::ops::Range;
use vek::*; use vek::*;
type SpriteVertex = <SpritePipeline as render::Pipeline>::Vertex; type SpriteVertex = <SpritePipeline as render::Pipeline>::Vertex;
@ -26,15 +28,31 @@ where
* &'a V: BaseVol<Vox=Cell>, */ * &'a V: BaseVol<Vox=Cell>, */
{ {
type Pipeline = TerrainPipeline; type Pipeline = TerrainPipeline;
type Result = Aabb<f32>; /// 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<f32>, Range<usize>);
type ShadowPipeline = ShadowPipeline; type ShadowPipeline = ShadowPipeline;
type Supplement = (&'b mut GreedyMesh<'a>, Vec3<f32>, Vec3<f32>); type Supplement = (
&'b mut GreedyMesh<'a>,
&'b mut Mesh<Self::Pipeline>,
Vec3<f32>,
Vec3<f32>,
);
type TranslucentPipeline = FigurePipeline; type TranslucentPipeline = FigurePipeline;
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn generate_mesh( fn generate_mesh(
self, self,
(greedy, offs, scale): Self::Supplement, (greedy, opaque_mesh, offs, scale): Self::Supplement,
) -> MeshGen<FigurePipeline, &'b mut GreedyMesh<'a>, Self> { ) -> MeshGen<FigurePipeline, &'b mut GreedyMesh<'a>, Self> {
let max_size = greedy.max_size(); let max_size = greedy.max_size();
// NOTE: Required because we steal two bits from the normal in the shadow uint // NOTE: Required because we steal two bits from the normal in the shadow uint
@ -43,17 +61,21 @@ where
// coordinate instead of 1 << 16. // coordinate instead of 1 << 16.
assert!(max_size.width.max(max_size.height) < 1 << 15); assert!(max_size.width.max(max_size.height) < 1 << 15);
let greedy_size = Vec3::new( let lower_bound = self.lower_bound();
(self.upper_bound().x - self.lower_bound().x + 1) as usize, let upper_bound = self.upper_bound();
(self.upper_bound().y - self.lower_bound().y + 1) as usize, assert!(
(self.upper_bound().z - self.lower_bound().z + 1) as usize, 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_::<usize>();
let greedy_size_cross = greedy_size; let greedy_size_cross = greedy_size;
let draw_delta = Vec3::new( let draw_delta = lower_bound;
self.lower_bound().x,
self.lower_bound().y,
self.lower_bound().z,
);
let get_light = |vol: &mut V, pos: Vec3<i32>| { let get_light = |vol: &mut V, pos: Vec3<i32>| {
if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { 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) TerrainVertex::new_figure(atlas_pos, (pos + offs) * scale, norm, 0)
}; };
let mut opaque_mesh = Mesh::new(); let start = opaque_mesh.vertices().len();
let bounds = greedy.push(GreedyConfig { greedy.push(GreedyConfig {
data: self, data: self,
draw_delta, draw_delta,
greedy_size, greedy_size,
@ -101,14 +123,20 @@ where
)); ));
}, },
}); });
let bounds = bounds.map(f32::from); let bounds = math::Aabb {
let bounds = Aabb { // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16.
min: (bounds.min + offs) * scale, min: math::Vec3::from((lower_bound.as_::<f32>() + offs) * scale),
max: (bounds.max + offs) * scale, max: math::Vec3::from((upper_bound.as_::<f32>() + offs) * scale),
} }
.made_valid(); .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 Pipeline = SpritePipeline;
type Result = (); type Result = ();
type ShadowPipeline = ShadowPipeline; type ShadowPipeline = ShadowPipeline;
type Supplement = (&'b mut GreedyMesh<'a>, bool); type Supplement = (&'b mut GreedyMesh<'a>, &'b mut Mesh<Self::Pipeline>, bool);
type TranslucentPipeline = SpritePipeline; type TranslucentPipeline = SpritePipeline;
#[allow(clippy::or_fun_call)] // TODO: Pending review in #587 #[allow(clippy::or_fun_call)] // TODO: Pending review in #587
fn generate_mesh( fn generate_mesh(
self, self,
(greedy, vertical_stripes): Self::Supplement, (greedy, opaque_mesh, vertical_stripes): Self::Supplement,
) -> MeshGen<SpritePipeline, &'b mut GreedyMesh<'a>, Self> { ) -> MeshGen<SpritePipeline, &'b mut GreedyMesh<'a>, Self> {
let max_size = greedy.max_size(); let max_size = greedy.max_size();
// NOTE: Required because we steal two bits from the normal in the shadow uint // NOTE: Required because we steal two bits from the normal in the shadow uint
@ -137,22 +165,25 @@ where
// coordinate instead of 1 << 16. // coordinate instead of 1 << 16.
assert!(max_size.width.max(max_size.height) < 1 << 16); assert!(max_size.width.max(max_size.height) < 1 << 16);
let greedy_size = Vec3::new( let lower_bound = self.lower_bound();
(self.upper_bound().x - self.lower_bound().x + 1) as usize, let upper_bound = self.upper_bound();
(self.upper_bound().y - self.lower_bound().y + 1) as usize, assert!(
(self.upper_bound().z - self.lower_bound().z + 1) as usize, 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!( assert!(
greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64,
"Sprite size out of bounds: {:?} ≤ (15, 15, 63)", "Sprite size out of bounds: {:?} ≤ (15, 15, 63)",
greedy_size - 1 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_::<usize>();
let greedy_size_cross = greedy_size; let greedy_size_cross = greedy_size;
let draw_delta = Vec3::new( let draw_delta = lower_bound;
self.lower_bound().x,
self.lower_bound().y,
self.lower_bound().z,
);
let get_light = |vol: &mut V, pos: Vec3<i32>| { let get_light = |vol: &mut V, pos: Vec3<i32>| {
if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) {
@ -177,8 +208,7 @@ where
let create_opaque = let create_opaque =
|atlas_pos, pos: Vec3<f32>, norm, _meta| SpriteVertex::new(atlas_pos, pos, norm); |atlas_pos, pos: Vec3<f32>, norm, _meta| SpriteVertex::new(atlas_pos, pos, norm);
let mut opaque_mesh = Mesh::new(); greedy.push(GreedyConfig {
let _bounds = greedy.push(GreedyConfig {
data: self, data: self,
draw_delta, draw_delta,
greedy_size, 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. // coordinate instead of 1 << 16.
assert!(max_size.width.max(max_size.height) < 1 << 16); assert!(max_size.width.max(max_size.height) < 1 << 16);
let greedy_size = Vec3::new( let lower_bound = self.lower_bound();
(self.upper_bound().x - self.lower_bound().x + 1) as usize, let upper_bound = self.upper_bound();
(self.upper_bound().y - self.lower_bound().y + 1) as usize, assert!(
(self.upper_bound().z - self.lower_bound().z + 1) as usize, 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!( assert!(
greedy_size.x <= 16 && greedy_size.y <= 16 && greedy_size.z <= 64, 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 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_::<usize>();
let greedy_size_cross = greedy_size; let greedy_size_cross = greedy_size;
let draw_delta = Vec3::new( let draw_delta = lower_bound;
self.lower_bound().x,
self.lower_bound().y,
self.lower_bound().z,
);
let get_light = |vol: &mut V, pos: Vec3<i32>| { let get_light = |vol: &mut V, pos: Vec3<i32>| {
if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) { if vol.get(pos).map(|vox| vox.is_empty()).unwrap_or(true) {
@ -269,7 +302,7 @@ where
let create_opaque = |_atlas_pos, pos: Vec3<f32>, norm| ParticleVertex::new(pos, norm); let create_opaque = |_atlas_pos, pos: Vec3<f32>, norm| ParticleVertex::new(pos, norm);
let mut opaque_mesh = Mesh::new(); let mut opaque_mesh = Mesh::new();
let _bounds = greedy.push(GreedyConfig { greedy.push(GreedyConfig {
data: self, data: self,
draw_delta, draw_delta,
greedy_size, greedy_size,
@ -304,7 +337,7 @@ fn should_draw_greedy(
let from = flat_get(pos - delta); let from = flat_get(pos - delta);
let to = flat_get(pos); let to = flat_get(pos);
let from_opaque = !from.is_empty(); let from_opaque = !from.is_empty();
if from_opaque == !to.is_empty() { if from_opaque != to.is_empty() {
None None
} else { } else {
// If going from transparent to opaque, backward facing; otherwise, forward // 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 from = flat_get(pos - delta);
let to = flat_get(pos); let to = flat_get(pos);
let from_opaque = !from.is_empty(); let from_opaque = !from.is_empty();
if from_opaque == !to.is_empty() { if from_opaque != to.is_empty() {
None None
} else { } else {
let faces_forward = from_opaque; let faces_forward = from_opaque;

View File

@ -7,7 +7,7 @@ use crate::{
}; };
use common::{ use common::{
terrain::{Block, BlockKind}, terrain::{Block, BlockKind},
vol::{DefaultVolIterator, ReadVol, RectRasterableVol, Vox}, vol::{ReadVol, RectRasterableVol, Vox},
volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d}, volumes::vol_grid_2d::{CachedVolGrid2d, VolGrid2d},
}; };
use std::{collections::VecDeque, fmt::Debug}; use std::{collections::VecDeque, fmt::Debug};
@ -40,7 +40,7 @@ impl Blendable for BlockKind {
} }
const SUNLIGHT: u8 = 24; const SUNLIGHT: u8 = 24;
const MAX_LIGHT_DIST: i32 = SUNLIGHT as i32; const _MAX_LIGHT_DIST: i32 = SUNLIGHT as i32;
fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>( fn calc_light<V: RectRasterableVol<Vox = Block> + ReadVol + Debug>(
bounds: Aabb<i32>, bounds: Aabb<i32>,
@ -241,9 +241,10 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
(range, max_texture_size): Self::Supplement, (range, max_texture_size): Self::Supplement,
) -> MeshGen<TerrainPipeline, FluidPipeline, Self> { ) -> MeshGen<TerrainPipeline, FluidPipeline, Self> {
// Find blocks that should glow // Find blocks that should glow
let lit_blocks = // FIXME: Replace with real lit blocks when we actually have blocks that glow.
DefaultVolIterator::new(self, range.min - MAX_LIGHT_DIST, range.max + MAX_LIGHT_DIST) let lit_blocks = core::iter::empty();
.filter_map(|(pos, block)| block.get_glow().map(|glow| (pos, glow))); /* 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 // Calculate chunk lighting
let mut light = calc_light(range, self, lit_blocks); let mut light = calc_light(range, self, lit_blocks);
@ -327,11 +328,18 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
let max_size = let max_size =
guillotiere::Size::new(i32::from(max_texture_size.x), i32::from(max_texture_size.y)); guillotiere::Size::new(i32::from(max_texture_size.x), i32::from(max_texture_size.y));
let greedy_size = Vec3::new( let greedy_size = Vec3::new(range.size().w - 2, range.size().h - 2, z_end - z_start + 1);
(range.size().w - 2) as usize, // NOTE: Terrain sizes are limited to 32 x 32 x 16384 (to fit in 24 bits: 5 + 5
(range.size().h - 2) as usize, // + 14). FIXME: Make this function fallible, since the terrain
(z_end - z_start + 1) as usize, // 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<f32> = greedy_size.as_::<f32>();
// 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_::<usize>();
let greedy_size_cross = Vec3::new(greedy_size.x - 1, greedy_size.y - 1, greedy_size.z); 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); let draw_delta = Vec3::new(1, 1, z_start);
@ -353,7 +361,7 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
let mut greedy = GreedyMesh::new(max_size); let mut greedy = GreedyMesh::new(max_size);
let mut opaque_mesh = Mesh::new(); let mut opaque_mesh = Mesh::new();
let mut fluid_mesh = Mesh::new(); let mut fluid_mesh = Mesh::new();
let bounds = greedy.push(GreedyConfig { greedy.push(GreedyConfig {
data: (), data: (),
draw_delta, draw_delta,
greedy_size, greedy_size,
@ -388,10 +396,11 @@ impl<'a, V: RectRasterableVol<Vox = Block> + ReadVol + Debug>
}, },
}); });
let bounds = bounds.map(f32::from); let min_bounds = mesh_delta;
let bounds = Aabb { let bounds = Aabb {
min: bounds.min + mesh_delta, // NOTE: Casts are safe since lower_bound and upper_bound both fit in a i16.
max: bounds.max + mesh_delta, min: min_bounds,
max: max_bounds + min_bounds,
}; };
let (col_lights, col_lights_size) = greedy.finalize(); let (col_lights, col_lights_size) = greedy.finalize();

View File

@ -25,12 +25,12 @@ impl<T: Copy + gfx::traits::Pod> Consts<T> {
vals: &[T], vals: &[T],
offset: usize, offset: usize,
) -> Result<(), RenderError> { ) -> Result<(), RenderError> {
if vals.len() > 0 { if vals.is_empty() {
Ok(())
} else {
encoder encoder
.update_buffer(&self.buf, vals, offset) .update_buffer(&self.buf, vals, offset)
.map_err(RenderError::UpdateError) .map_err(RenderError::UpdateError)
} else {
Ok(())
} }
} }
} }

View File

@ -15,7 +15,7 @@ impl<T: Copy + gfx::traits::Pod> Instances<T> {
pub fn new(factory: &mut gfx_backend::Factory, len: usize) -> Result<Self, RenderError> { pub fn new(factory: &mut gfx_backend::Factory, len: usize) -> Result<Self, RenderError> {
Ok(Self { Ok(Self {
ibuf: factory 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)?, .map_err(RenderError::BufferCreationError)?,
}) })
} }

View File

@ -1,5 +1,5 @@
use super::Pipeline; 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. /// A `Vec`-based mesh structure used to store mesh data on the CPU.
pub struct Mesh<P: Pipeline> { pub struct Mesh<P: Pipeline> {
@ -69,6 +69,11 @@ impl<P: Pipeline> Mesh<P> {
} }
pub fn iter(&self) -> std::slice::Iter<P::Vertex> { self.verts.iter() } pub fn iter(&self) -> std::slice::Iter<P::Vertex> { self.verts.iter() }
/// NOTE: Panics if vertex_range is out of bounds of vertices.
pub fn iter_mut(&mut self, vertex_range: Range<usize>) -> std::slice::IterMut<P::Vertex> {
self.verts[vertex_range].iter_mut()
}
} }
impl<P: Pipeline> IntoIterator for Mesh<P> { impl<P: Pipeline> IntoIterator for Mesh<P> {

View File

@ -244,7 +244,7 @@ impl core::convert::TryFrom<ShadowMode> for ShadowMapMode {
} }
impl ShadowMode { 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 /// Render modes

View File

@ -22,6 +22,15 @@ impl<P: Pipeline> Model<P> {
} }
pub fn vertex_range(&self) -> Range<u32> { self.vertex_range.clone() } pub fn vertex_range(&self) -> Range<u32> { 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<u32>) -> Model<P> {
Model {
vbuf: self.vbuf.clone(),
vertex_range,
}
}
} }
/// Represents a mesh on the GPU which can be updated dynamically. /// Represents a mesh on the GPU which can be updated dynamically.
@ -40,10 +49,10 @@ impl<P: Pipeline> DynamicModel<P> {
/// Create a model with a slice of a portion of this model to send to the /// Create a model with a slice of a portion of this model to send to the
/// renderer. /// renderer.
pub fn submodel(&self, range: Range<usize>) -> Model<P> { pub fn submodel(&self, vertex_range: Range<u32>) -> Model<P> {
Model { Model {
vbuf: self.vbuf.clone(), vbuf: self.vbuf.clone(),
vertex_range: range.start as u32..range.end as u32, vertex_range,
} }
} }

View File

@ -1,11 +1,9 @@
use super::{ use super::{
super::{ super::{Mesh, Model, Pipeline, TerrainPipeline, TgtColorFmt, TgtDepthStencilFmt},
ColLightFmt, Mesh, Model, Pipeline, TerrainPipeline, Texture, TgtColorFmt,
TgtDepthStencilFmt,
},
shadow, Globals, Light, Shadow, shadow, Globals, Light, Shadow,
}; };
use crate::mesh::greedy::GreedyMesh; use crate::mesh::greedy::GreedyMesh;
use core::ops::Range;
use gfx::{ use gfx::{
self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline,
gfx_pipeline_inner, gfx_vertex_struct_meta, state::ColorMask, gfx_pipeline_inner, gfx_vertex_struct_meta, state::ColorMask,
@ -117,12 +115,9 @@ impl Pipeline for FigurePipeline {
} }
pub struct FigureModel { pub struct FigureModel {
pub bounds: Aabb<f32>,
pub opaque: Model<TerrainPipeline>, pub opaque: Model<TerrainPipeline>,
// TODO: Consider using mipmaps instead of storing multiple texture atlases for different LOD /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different
// levels. * LOD levels. */
pub col_lights: Texture<ColLightFmt>,
pub allocation: guillotiere::Allocation,
} }
impl FigureModel { impl FigureModel {
@ -137,7 +132,4 @@ impl FigureModel {
} }
} }
pub type BoneMeshes = ( pub type BoneMeshes = (Mesh<TerrainPipeline>, (anim::vek::Aabb<f32>, Range<usize>));
Mesh</* FigurePipeline */ TerrainPipeline>, /* , Mesh<ShadowPipeline> */
Aabb<f32>,
);

View File

@ -7,15 +7,11 @@ use super::{
}; };
use gfx::{ use gfx::{
self, gfx_constant_struct_meta, gfx_defines, gfx_impl_struct_meta, gfx_pipeline, 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::*; use vek::*;
gfx_defines! { gfx_defines! {
vertex Vertex {
pos_norm: u32 = "v_pos_norm",
}
constant Locals { constant Locals {
shadow_matrices: [[f32; 4]; 4] = "shadowMatrices", shadow_matrices: [[f32; 4]; 4] = "shadowMatrices",
texture_mats: [[f32; 4]; 4] = "texture_mat", texture_mats: [[f32; 4]; 4] = "texture_mat",
@ -55,54 +51,6 @@ gfx_defines! {
} }
} }
impl Vertex {
pub fn new(pos: Vec3<f32>, norm: Vec3<f32>, 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<f32>, norm: Vec3<f32>, 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 { impl Locals {
pub fn new(shadow_mat: Mat4<f32>, texture_mat: Mat4<f32>) -> Self { pub fn new(shadow_mat: Mat4<f32>, texture_mat: Mat4<f32>) -> Self {
Self { Self {
@ -138,5 +86,5 @@ impl ShadowPipeline {
} }
impl Pipeline for ShadowPipeline { impl Pipeline for ShadowPipeline {
type Vertex = Vertex; type Vertex = terrain::Vertex;
} }

View File

@ -101,16 +101,13 @@ impl Vertex {
}; };
Self { Self {
// pos_norm: 0 // pos_norm: ((pos.x as u32) & 0x003F)
// | ((pos.x as u32) & 0x003F) << 0
// | ((pos.y as u32) & 0x003F) << 6 // | ((pos.y as u32) & 0x003F) << 6
// | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12 // | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12
// | if meta { 1 } else { 0 } << 28 // | if meta { 1 } else { 0 } << 28
// | (norm_bits & 0x7) << 29, // | (norm_bits & 0x7) << 29,
pos: pos.into_array(), pos: pos.into_array(),
atlas_pos: 0 atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) | ((atlas_pos.y as u32) & 0xFFFF) << 16,
| ((atlas_pos.x as u32) & 0xFFFF) << 0
| ((atlas_pos.y as u32) & 0xFFFF) << 16,
norm_ao: norm_bits, norm_ao: norm_bits,
} }
} }
@ -122,8 +119,7 @@ impl Instance {
let mat_arr = mat.into_col_arrays(); let mat_arr = mat.into_col_arrays();
Self { Self {
pos_ori: 0 pos_ori: ((pos.x as u32) & 0x003F)
| ((pos.x as u32) & 0x003F) << 0
| ((pos.y as u32) & 0x003F) << 6 | ((pos.y as u32) & 0x003F) << 6
| (((pos + EXTRA_NEG_Z).z.max(0).min(1 << 16) as u32) & 0xFFFF) << 12 | (((pos + EXTRA_NEG_Z).z.max(0).min(1 << 16) as u32) & 0xFFFF) << 12
| (u32::from(ori_bits) & 0x7) << 29, | (u32::from(ori_bits) & 0x7) << 29,

View File

@ -61,15 +61,12 @@ impl Vertex {
5 5
}; };
Self { Self {
pos_norm: 0 pos_norm: ((pos.x as u32) & 0x003F) << 0
| ((pos.x as u32) & 0x003F) << 0
| ((pos.y as u32) & 0x003F) << 6 | ((pos.y as u32) & 0x003F) << 6
| (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12 | (((pos + EXTRA_NEG_Z).z.max(0.0).min((1 << 16) as f32) as u32) & 0xFFFF) << 12
| if meta { 1 } else { 0 } << 28 | if meta { 1 } else { 0 } << 28
| (norm_bits & 0x7) << 29, | (norm_bits & 0x7) << 29,
atlas_pos: 0 atlas_pos: ((atlas_pos.x as u32) & 0xFFFF) << 0 | ((atlas_pos.y as u32) & 0xFFFF) << 16,
| ((atlas_pos.x as u32) & 0xFFFF) << 0
| ((atlas_pos.y as u32) & 0xFFFF) << 16,
} }
} }
@ -94,8 +91,7 @@ impl Vertex {
.reduce_bitor() .reduce_bitor()
| (((bone_idx & 0xF) as u32) << 27) | (((bone_idx & 0xF) as u32) << 27)
| (norm_bits << 31), | (norm_bits << 31),
atlas_pos: 0 atlas_pos: ((atlas_pos.x as u32) & 0x7FFF) << 2
| ((atlas_pos.x as u32) & 0x7FFF) << 2
| ((atlas_pos.y as u32) & 0x7FFF) << 17 | ((atlas_pos.y as u32) & 0x7FFF) << 17
| axis_bits & 3, | axis_bits & 3,
} }
@ -109,11 +105,9 @@ impl Vertex {
[col.r, col.g, col.b, light] [col.r, col.g, col.b, light]
} }
pub fn with_bone_idx(self, bone_idx: u8) -> Self { /// Set the bone_idx for an existing figure vertex.
Self { pub fn set_bone_idx(&mut self, bone_idx: u8) {
pos_norm: (self.pos_norm & !(0xF << 27)) | ((bone_idx as u32 & 0xF) << 27), self.pos_norm = (self.pos_norm & !(0xF << 27)) | ((bone_idx as u32 & 0xF) << 27);
..self
}
} }
} }

View File

@ -1002,7 +1002,7 @@ impl Renderer {
pub fn render_figure( pub fn render_figure(
&mut self, &mut self,
model: &figure::FigureModel, model: &figure::FigureModel,
_col_lights: &Texture<ColLightFmt>, col_lights: &Texture<ColLightFmt>,
global: &GlobalModel, global: &GlobalModel,
locals: &Consts<figure::Locals>, locals: &Consts<figure::Locals>,
bones: &Consts<figure::BoneData>, bones: &Consts<figure::BoneData>,
@ -1026,7 +1026,6 @@ impl Renderer {
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
) )
}; };
let col_lights = &model.col_lights;
let model = &model.opaque; let model = &model.opaque;
self.encoder.draw( self.encoder.draw(
@ -1087,10 +1086,7 @@ impl Renderer {
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
) )
}; };
let col_lights = &model.col_lights;
let model = &model.opaque; let model = &model.opaque;
// let atlas_model = &model.opaque;
// let model = &model.shadow;
self.encoder.draw( self.encoder.draw(
&gfx::Slice { &gfx::Slice {
@ -1103,9 +1099,7 @@ impl Renderer {
&self.player_shadow_pipeline.pso, &self.player_shadow_pipeline.pso,
&figure::pipe::Data { &figure::pipe::Data {
vbuf: model.vbuf.clone(), vbuf: model.vbuf.clone(),
// abuf: atlas_model.vbuf.clone(),
col_lights: (col_lights.srv.clone(), col_lights.sampler.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(), locals: locals.buf.clone(),
globals: global.globals.buf.clone(), globals: global.globals.buf.clone(),
bones: bones.buf.clone(), bones: bones.buf.clone(),
@ -1127,7 +1121,7 @@ impl Renderer {
pub fn render_player( pub fn render_player(
&mut self, &mut self,
model: &figure::FigureModel, model: &figure::FigureModel,
_col_lights: &Texture<ColLightFmt>, col_lights: &Texture<ColLightFmt>,
global: &GlobalModel, global: &GlobalModel,
locals: &Consts<figure::Locals>, locals: &Consts<figure::Locals>,
bones: &Consts<figure::BoneData>, bones: &Consts<figure::BoneData>,
@ -1151,7 +1145,6 @@ impl Renderer {
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()), (self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
) )
}; };
let col_lights = &model.col_lights;
let model = &model.opaque; let model = &model.opaque;
self.encoder.draw( self.encoder.draw(
@ -1476,6 +1469,10 @@ impl Renderer {
ibuf: instances.ibuf.clone(), ibuf: instances.ibuf.clone(),
col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()), col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()),
terrain_locals: terrain_locals.buf.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(), locals: locals.buf.clone(),
globals: global.globals.buf.clone(), globals: global.globals.buf.clone(),
lights: global.lights.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. /// Queue the rendering of the provided UI element in the upcoming frame.
pub fn render_ui_element<F: gfx::format::Formatted<View = [f32; 4]>>( pub fn render_ui_element<F: gfx::format::Formatted<View = [f32; 4]>>(
&mut self, &mut self,
model: &Model<ui::UiPipeline>, model: Model<ui::UiPipeline>,
tex: &Texture<F>, tex: &Texture<F>,
scissor: Aabr<u16>, scissor: Aabr<u16>,
globals: &Consts<Globals>, globals: &Consts<Globals>,
@ -1594,15 +1591,15 @@ impl Renderer {
let Aabr { min, max } = scissor; let Aabr { min, max } = scissor;
self.encoder.draw( self.encoder.draw(
&gfx::Slice { &gfx::Slice {
start: model.vertex_range().start, start: model.vertex_range.start,
end: model.vertex_range().end, end: model.vertex_range.end,
base_vertex: 0, base_vertex: 0,
instances: None, instances: None,
buffer: gfx::IndexBuffer::Auto, buffer: gfx::IndexBuffer::Auto,
}, },
&self.ui_pipeline.pso, &self.ui_pipeline.pso,
&ui::pipe::Data { &ui::pipe::Data {
vbuf: model.vbuf.clone(), vbuf: model.vbuf,
scissor: gfx::Rect { scissor: gfx::Rect {
x: min.x, x: min.x,
y: min.y, y: min.y,

View File

@ -1,7 +1,7 @@
use super::load::*; use super::{load::*, FigureModelEntry};
use crate::{ use crate::{
mesh::{greedy::GreedyMesh, Meshable}, mesh::{greedy::GreedyMesh, Meshable},
render::{BoneMeshes, FigureModel, FigurePipeline, Mesh, Renderer}, render::{BoneMeshes, FigureModel, FigurePipeline, Mesh, Renderer, TerrainPipeline},
scene::camera::CameraMode, scene::camera::CameraMode,
}; };
use anim::Skeleton; use anim::Skeleton;
@ -22,7 +22,7 @@ use core::convert::TryInto;
use hashbrown::{hash_map::Entry, HashMap}; use hashbrown::{hash_map::Entry, HashMap};
use vek::*; use vek::*;
pub type FigureModelEntry = [FigureModel; 3]; pub type FigureModelEntryLod = FigureModelEntry<3>;
#[derive(Eq, Hash, PartialEq)] #[derive(Eq, Hash, PartialEq)]
struct FigureKey { struct FigureKey {
@ -198,7 +198,7 @@ pub struct FigureModelCache<Skel = anim::character::CharacterSkeleton>
where where
Skel: Skeleton, Skel: Skeleton,
{ {
models: HashMap<FigureKey, ((FigureModelEntry, Skel::Attr), u64)>, models: HashMap<FigureKey, ((FigureModelEntryLod, Skel::Attr), u64)>,
manifest_indicator: ReloadIndicator, manifest_indicator: ReloadIndicator,
} }
@ -980,7 +980,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
tick: u64, tick: u64,
camera_mode: CameraMode, camera_mode: CameraMode,
character_state: Option<&CharacterState>, character_state: Option<&CharacterState>,
) -> &(FigureModelEntry, Skel::Attr) ) -> &(FigureModelEntryLod, Skel::Attr)
where where
for<'a> &'a common::comp::Body: std::convert::TryInto<Skel::Attr>, for<'a> &'a common::comp::Body: std::convert::TryInto<Skel::Attr>,
Skel::Attr: Default, Skel::Attr: Default,
@ -1011,78 +1011,137 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
.unwrap_or_else(<Skel::Attr as Default>::default); .unwrap_or_else(<Skel::Attr as Default>::default);
let manifest_indicator = &mut self.manifest_indicator; let manifest_indicator = &mut self.manifest_indicator;
let mut make_model = let mut greedy = FigureModel::make_greedy();
|generate_mesh: for<'a> fn(&mut GreedyMesh<'a>, _, _) -> _| { let mut opaque = Mesh::<TerrainPipeline>::new();
let mut greedy = FigureModel::make_greedy(); // Choose the most conservative bounds for any LOD model.
let mut opaque = Mesh::new(); let mut figure_bounds = anim::vek::Aabb {
let mut figure_bounds = Aabb { min: anim::vek::Vec3::zero(),
min: Vec3::zero(), max: anim::vek::Vec3::zero(),
max: 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| { Self::bone_meshes(key, manifest_indicator, |segment, offset| {
generate_mesh(&mut greedy, segment, offset) generate_mesh(&mut greedy, &mut opaque, segment, offset)
}) });
meshes
.iter() .iter()
.enumerate() .enumerate()
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm))) // NOTE: Cast to u8 is safe because i <= 16.
.for_each(|(i, (opaque_mesh, bounds))| { .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 opaque
.push_mesh_map(opaque_mesh, |vert| vert.with_bone_idx(i as u8)); .iter_mut(vertex_range)
figure_bounds.expand_to_contain(*bounds); .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 // NOTE: vertex_start and vertex_end *should* fit in a u32, by the
.create_figure(renderer, greedy, (opaque, figure_bounds)) // following logic:
.unwrap() //
}; // 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>( fn generate_mesh<'a>(
greedy: &mut GreedyMesh<'a>, greedy: &mut GreedyMesh<'a>,
opaque_mesh: &mut Mesh<TerrainPipeline>,
segment: Segment, segment: Segment,
offset: Vec3<f32>, offset: Vec3<f32>,
) -> BoneMeshes { ) -> BoneMeshes {
let (opaque, _, _, bounds) = let (opaque, _, _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh( Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment, segment,
(greedy, offset, Vec3::one()), (greedy, opaque_mesh, offset, Vec3::one()),
); );
(opaque, bounds) (opaque, bounds)
} }
fn generate_mesh_lod_mid<'a>( fn generate_mesh_lod_mid<'a>(
greedy: &mut GreedyMesh<'a>, greedy: &mut GreedyMesh<'a>,
opaque_mesh: &mut Mesh<TerrainPipeline>,
segment: Segment, segment: Segment,
offset: Vec3<f32>, offset: Vec3<f32>,
) -> BoneMeshes { ) -> BoneMeshes {
let lod_scale = Vec3::broadcast(0.6); let lod_scale = 0.6;
let (opaque, _, _, bounds) = let (opaque, _, _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh( Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment.scaled_by(lod_scale), segment.scaled_by(Vec3::broadcast(lod_scale)),
(greedy, offset * lod_scale, Vec3::one() / lod_scale), (
greedy,
opaque_mesh,
offset * lod_scale,
Vec3::one() / lod_scale,
),
); );
(opaque, bounds) (opaque, bounds)
} }
fn generate_mesh_lod_low<'a>( fn generate_mesh_lod_low<'a>(
greedy: &mut GreedyMesh<'a>, greedy: &mut GreedyMesh<'a>,
opaque_mesh: &mut Mesh<TerrainPipeline>,
segment: Segment, segment: Segment,
offset: Vec3<f32>, offset: Vec3<f32>,
) -> BoneMeshes { ) -> BoneMeshes {
let lod_scale = Vec3::broadcast(0.3); let lod_scale = 0.3;
let segment = segment.scaled_by(lod_scale);
let (opaque, _, _, bounds) = let (opaque, _, _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh( Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment, segment.scaled_by(Vec3::broadcast(lod_scale)),
(greedy, offset * lod_scale, Vec3::one() / lod_scale), (
greedy,
opaque_mesh,
offset * lod_scale,
Vec3::one() / lod_scale,
),
); );
(opaque, bounds) (opaque, bounds)
} }
let models = [
make_model(generate_mesh),
make_model(generate_mesh_lod_mid),
make_model(generate_mesh_lod_low),
];
( (
[ col_lights
make_model(generate_mesh), .create_figure(renderer, greedy, (opaque, figure_bounds), models)
make_model(generate_mesh_lod_mid), .expect("Failed to upload figure data to the GPU!"),
make_model(generate_mesh_lod_low),
],
skeleton_attr, skeleton_attr,
) )
}; };
@ -1100,14 +1159,12 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
} }
// TODO: Don't hard-code this. // TODO: Don't hard-code this.
if tick % 60 == 0 { 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. // Wait about a minute at 60 fps before invalidating old models.
let delta = 60 * 60; let delta = 60 * 60;
let alive = *last_used + delta > tick; let alive = *last_used + delta > tick;
if !alive { if !alive {
models.iter().for_each(|model| { col_lights.atlas.deallocate(model_entry.allocation.id);
col_lights.atlas.deallocate(model.allocation.id);
});
} }
alive alive
}); });

View File

@ -760,7 +760,7 @@ impl HumMainWeaponSpec {
let tool_kind = if let Some(kind) = tool_kind { let tool_kind = if let Some(kind) = tool_kind {
kind kind
} else { } else {
return (Mesh::new(), Aabb::default()); return (Mesh::new(), (anim::vek::Aabb::default(), 0..0));
}; };
let spec = match self.0.get(tool_kind) { let spec = match self.0.get(tool_kind) {

View File

@ -8,8 +8,8 @@ use crate::{
ecs::comp::Interpolated, ecs::comp::Interpolated,
mesh::greedy::GreedyMesh, mesh::greedy::GreedyMesh,
render::{ render::{
BoneMeshes, ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, GlobalModel, ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, GlobalModel, Mesh,
RenderError, Renderer, ShadowPipeline, Texture, RenderError, Renderer, ShadowPipeline, TerrainPipeline, Texture,
}, },
scene::{ scene::{
camera::{Camera, CameraMode, Dependents}, camera::{Camera, CameraMode, Dependents},
@ -36,8 +36,9 @@ use common::{
}; };
use core::{ use core::{
borrow::Borrow, borrow::Borrow,
convert::TryFrom,
hash::Hash, hash::Hash,
ops::{Deref, DerefMut}, ops::{Deref, DerefMut, Range},
}; };
use guillotiere::AtlasAllocator; use guillotiere::AtlasAllocator;
use hashbrown::HashMap; use hashbrown::HashMap;
@ -51,6 +52,33 @@ const MOVING_THRESHOLD_SQR: f32 = MOVING_THRESHOLD * MOVING_THRESHOLD;
/// camera data, fiigure LOD render distance. /// camera data, fiigure LOD render distance.
pub type CameraData<'a> = (&'a Camera, f32); pub type CameraData<'a> = (&'a Camera, f32);
/// Enough data to render a figure model.
pub type FigureModelRef<'a> = (
&'a Consts<FigureLocals>,
&'a Consts<FigureBoneData>,
&'a FigureModel,
&'a Texture<ColLightFmt>,
);
/// An entry holding enough information to draw or destroy a figure in a
/// particular cache.
pub struct FigureModelEntry<const N: usize> {
/// The estimated bounds of this figure, in voxels. This may not be very
/// useful yet.
_bounds: math::Aabb<f32>,
/// 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<ColLightFmt>,
/// Models stored in this figure entry; there may be several for one figure,
/// because of LOD models.
pub models: [FigureModel; N],
}
struct FigureMgrStates { struct FigureMgrStates {
character_states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>, character_states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
quadruped_small_states: HashMap<EcsEntity, FigureState<QuadrupedSmallSkeleton>>, quadruped_small_states: HashMap<EcsEntity, FigureState<QuadrupedSmallSkeleton>>,
@ -1018,7 +1046,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1119,7 +1147,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1220,7 +1248,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1319,7 +1347,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1415,7 +1443,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1500,7 +1528,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1581,7 +1609,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1663,7 +1691,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1748,7 +1776,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1833,7 +1861,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -1929,7 +1957,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -2011,7 +2039,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
in_frustum, in_frustum,
is_player, is_player,
@ -2043,7 +2071,7 @@ impl FigureMgr {
col, col,
dt, dt,
state_animation_rate, state_animation_rate,
&model[0], &model,
lpindex, lpindex,
true, true,
is_player, is_player,
@ -2159,14 +2187,7 @@ impl FigureMgr {
figure_lod_render_distance, figure_lod_render_distance,
|state| state.visible(), |state| state.visible(),
) { ) {
renderer.render_figure( renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod);
model,
&col_lights.col_lights,
global,
locals,
bone_consts,
lod,
);
} }
} }
} }
@ -2215,17 +2236,10 @@ impl FigureMgr {
figure_lod_render_distance, figure_lod_render_distance,
|state| state.visible(), |state| state.visible(),
) { ) {
renderer.render_player( renderer.render_player(model, &col_lights, global, locals, bone_consts, lod);
model,
&col_lights.col_lights,
global,
locals,
bone_consts,
lod,
);
renderer.render_player_shadow( renderer.render_player_shadow(
model, model,
&col_lights.col_lights, &col_lights,
global, global,
bone_consts, bone_consts,
lod, lod,
@ -2246,16 +2260,10 @@ impl FigureMgr {
body: &Body, body: &Body,
loadout: Option<&Loadout>, loadout: Option<&Loadout>,
is_player: bool, is_player: bool,
// is_shadow: bool,
pos: vek::Vec3<f32>, pos: vek::Vec3<f32>,
figure_lod_render_distance: f32, figure_lod_render_distance: f32,
filter_state: impl Fn(&FigureStateMeta) -> bool, filter_state: impl Fn(&FigureStateMeta) -> bool,
) -> Option<( ) -> Option<FigureModelRef> {
&Consts<FigureLocals>,
&Consts<FigureBoneData>,
&FigureModel,
&FigureColLights,
)> {
let player_camera_mode = if is_player { let player_camera_mode = if is_player {
camera.get_mode() camera.get_mode()
} else { } else {
@ -2297,7 +2305,7 @@ impl FigureMgr {
}, },
} = self; } = self;
let col_lights = &mut *col_lights_; 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 Body::Humanoid(_) => character_states
.get(&entity) .get(&entity)
.filter(|state| filter_state(&*state)) .filter(|state| filter_state(&*state))
@ -2563,14 +2571,14 @@ impl FigureMgr {
let figure_mid_detail_distance = figure_lod_render_distance * 0.5; 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) { 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) { } else if pos.distance_squared(cam_pos) > figure_mid_detail_distance.powf(2.0) {
&model[1] &model_entry.models[1]
} else { } 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 { } else {
// trace!("Body has no saved figure"); // trace!("Body has no saved figure");
None None
@ -2584,24 +2592,39 @@ impl FigureMgr {
pub struct FigureColLights { pub struct FigureColLights {
atlas: AtlasAllocator, atlas: AtlasAllocator,
col_lights: Texture<ColLightFmt>, // col_lights: Texture<ColLightFmt>,
} }
impl FigureColLights { impl FigureColLights {
pub fn new(renderer: &mut Renderer) -> Self { pub fn new(renderer: &mut Renderer) -> Self {
let (atlas, col_lights) = let atlas = Self::make_atlas(renderer).expect("Failed to create texture atlas for figures");
Self::make_atlas(renderer).expect("Failed to create texture atlas for figures"); Self {
Self { atlas, col_lights } atlas, /* col_lights, */
}
} }
pub fn texture(&self) -> &Texture<ColLightFmt> { &self.col_lights } /// Find the correct texture for this model entry.
pub fn texture<'a, const N: usize>(
&'a self,
model: &'a FigureModelEntry<N>,
) -> &'a Texture<ColLightFmt> {
/* &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, &mut self,
renderer: &mut Renderer, renderer: &mut Renderer,
greedy: GreedyMesh<'a>, greedy: GreedyMesh<'a>,
(opaque, bounds): BoneMeshes, (opaque, bounds): (Mesh<TerrainPipeline>, math::Aabb<f32>),
) -> Result<FigureModel, RenderError> { vertex_range: [Range<u32>; N],
) -> Result<FigureModelEntry<N>, RenderError> {
let (tex, tex_size) = greedy.finalize(); let (tex, tex_size) = greedy.finalize();
let atlas = &mut self.atlas; let atlas = &mut self.atlas;
let allocation = atlas let allocation = atlas
@ -2609,21 +2632,32 @@ impl FigureColLights {
i32::from(tex_size.x), i32::from(tex_size.x),
i32::from(tex_size.y), 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 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 { Ok(FigureModelEntry {
bounds, _bounds: bounds,
opaque: renderer.create_model(&opaque)?, models: vertex_range.map(|range| {
// shadow: renderer.create_model(&shadow)?, 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, col_lights,
allocation, allocation,
}) })
} }
fn make_atlas( fn make_atlas(renderer: &mut Renderer) -> Result<AtlasAllocator, RenderError> {
renderer: &mut Renderer,
) -> Result<(AtlasAllocator, Texture<ColLightFmt>), RenderError> {
let max_texture_size = renderer.max_texture_size(); let max_texture_size = renderer.max_texture_size();
let atlas_size = let atlas_size =
guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
@ -2633,7 +2667,11 @@ impl FigureColLights {
large_size_threshold: 256, large_size_threshold: 256,
..guillotiere::AllocatorOptions::default() ..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( gfx::texture::Kind::D2(
max_texture_size, max_texture_size,
max_texture_size, max_texture_size,
@ -2649,7 +2687,8 @@ impl FigureColLights {
gfx::texture::WrapMode::Clamp, gfx::texture::WrapMode::Clamp,
), ),
)?; )?;
Ok((atlas, texture)) Ok((atlas, texture)) */
Ok(atlas)
} }
} }
@ -2714,7 +2753,7 @@ impl<S: Skeleton> FigureState<S> {
} }
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587 #[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
pub fn update( pub fn update<const N: usize>(
&mut self, &mut self,
renderer: &mut Renderer, renderer: &mut Renderer,
pos: anim::vek::Vec3<f32>, pos: anim::vek::Vec3<f32>,
@ -2723,7 +2762,7 @@ impl<S: Skeleton> FigureState<S> {
col: vek::Rgba<f32>, col: vek::Rgba<f32>,
dt: f32, dt: f32,
state_animation_rate: f32, state_animation_rate: f32,
model: &FigureModel, model: &FigureModelEntry<N>,
_lpindex: u8, _lpindex: u8,
_visible: bool, _visible: bool,
is_player: bool, is_player: bool,
@ -2736,8 +2775,8 @@ impl<S: Skeleton> FigureState<S> {
// largest dimension (if we were exact, it should just be half the largest // 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 // dimension, but we're not, so we double it and use size() instead of
// half_size()). // half_size()).
let radius = model.bounds.half_size().reduce_partial_max(); /* let radius = vek::Extent3::<f32>::from(model.bounds.half_size()).reduce_partial_max();
let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); */
self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt); self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt);

View File

@ -82,10 +82,10 @@ fn create_lod_terrain_mesh(detail: u32) -> Mesh<LodTerrainPipeline> {
let transform = |x| (2.0 * x as f32) / detail as f32 - 1.0; let transform = |x| (2.0 * x as f32) / detail as f32 - 1.0;
Quad::new( Quad::new(
Vertex::new(Vec2::new(x + 0, y + 0).map(transform)), Vertex::new(Vec2::new(x, y).map(transform)),
Vertex::new(Vec2::new(x + 1, y + 0).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 + 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) { .rotated_by(if (x > detail as i32 / 2) ^ (y > detail as i32 / 2) {
0 0

View File

@ -994,7 +994,7 @@ impl Scene {
let camera_data = (&self.camera, scene_data.figure_lod_render_distance); let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
// would instead have this as an extension. // 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 { if is_daylight {
// Set up shadow mapping. // Set up shadow mapping.
renderer.start_shadows(); renderer.start_shadows();

View File

@ -53,8 +53,9 @@ impl ParticleMgr {
power, power,
reagent, reagent,
} => { } => {
for _ in 0..150 { self.particles.resize(
self.particles.push(Particle::new( self.particles.len() + 150,
Particle::new(
Duration::from_millis(if reagent.is_some() { 1000 } else { 250 }), Duration::from_millis(if reagent.is_some() { 1000 } else { 250 }),
time, time,
match reagent { match reagent {
@ -66,17 +67,18 @@ impl ParticleMgr {
None => ParticleMode::Shrapnel, None => ParticleMode::Shrapnel,
}, },
*pos, *pos,
)); ),
} );
for _ in 0..200 { self.particles.resize(
self.particles.push(Particle::new( self.particles.len() + 200,
Particle::new(
Duration::from_secs(4), Duration::from_secs(4),
time, time,
ParticleMode::CampfireSmoke, ParticleMode::CampfireSmoke,
*pos + Vec2::<f32>::zero().map(|_| rng.gen_range(-1.0, 1.0) * power), *pos + Vec2::<f32>::zero().map(|_| rng.gen_range(-1.0, 1.0) * power),
)); ),
} );
}, },
Outcome::ProjectileShot { .. } => {}, Outcome::ProjectileShot { .. } => {},
} }
@ -107,14 +109,7 @@ impl ParticleMgr {
fn maintain_body_particles(&mut self, scene_data: &SceneData) { fn maintain_body_particles(&mut self, scene_data: &SceneData) {
let ecs = scene_data.state.ecs(); let ecs = scene_data.state.ecs();
for (_i, (_entity, body, pos)) in ( for (body, pos) in (&ecs.read_storage::<Body>(), &ecs.read_storage::<Pos>()).join() {
&ecs.entities(),
&ecs.read_storage::<Body>(),
&ecs.read_storage::<Pos>(),
)
.join()
.enumerate()
{
match body { match body {
Body::Object(object::Body::CampfireLit) => { Body::Object(object::Body::CampfireLit) => {
self.maintain_campfirelit_particles(scene_data, pos) self.maintain_campfirelit_particles(scene_data, pos)
@ -181,24 +176,26 @@ impl ParticleMgr {
let time = scene_data.state.get_time(); let time = scene_data.state.get_time();
// fire // fire
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(3)) { self.particles.resize(
self.particles.push(Particle::new( self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(3))),
Particle::new(
Duration::from_millis(250), Duration::from_millis(250),
time, time,
ParticleMode::CampfireFire, ParticleMode::CampfireFire,
pos.0, pos.0,
)); ),
} );
// smoke // smoke
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(5)) { self.particles.resize(
self.particles.push(Particle::new( self.particles.len() + usize::from(self.scheduler.heartbeats(Duration::from_millis(5))),
Particle::new(
Duration::from_secs(2), Duration::from_secs(2),
time, time,
ParticleMode::CampfireSmoke, ParticleMode::CampfireSmoke,
pos.0, pos.0,
)); ),
} );
} }
fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos) { fn maintain_bomb_particles(&mut self, scene_data: &SceneData, pos: &Pos) {
@ -228,23 +225,23 @@ impl ParticleMgr {
let ecs = state.ecs(); let ecs = state.ecs();
let time = state.get_time(); let time = state.get_time();
for (_i, (_entity, pos, character_state)) in ( for (pos, character_state) in (
&ecs.entities(),
&ecs.read_storage::<Pos>(), &ecs.read_storage::<Pos>(),
&ecs.read_storage::<CharacterState>(), &ecs.read_storage::<CharacterState>(),
) )
.join() .join()
.enumerate()
{ {
if let CharacterState::Boost(_) = character_state { if let CharacterState::Boost(_) = character_state {
for _ in 0..self.scheduler.heartbeats(Duration::from_millis(10)) { self.particles.resize(
self.particles.push(Particle::new( self.particles.len()
+ usize::from(self.scheduler.heartbeats(Duration::from_millis(10))),
Particle::new(
Duration::from_secs(15), Duration::from_secs(15),
time, time,
ParticleMode::CampfireSmoke, ParticleMode::CampfireSmoke,
pos.0, pos.0,
)); ),
} );
} }
} }
} }
@ -393,6 +390,7 @@ impl HeartbeatScheduler {
pub fn clear(&mut self) { self.timers.clear() } pub fn clear(&mut self) { self.timers.clear() }
} }
#[derive(Clone, Copy)]
struct Particle { struct Particle {
alive_until: f64, // created_at + lifespan alive_until: f64, // created_at + lifespan
instance: ParticleInstance, instance: ParticleInstance,

View File

@ -2,12 +2,12 @@ use crate::{
mesh::{greedy::GreedyMesh, Meshable}, mesh::{greedy::GreedyMesh, Meshable},
render::{ render::{
create_pp_mesh, create_skybox_mesh, BoneMeshes, Consts, FigureModel, FigurePipeline, create_pp_mesh, create_skybox_mesh, BoneMeshes, Consts, FigureModel, FigurePipeline,
GlobalModel, Globals, Light, Model, PostProcessLocals, PostProcessPipeline, Renderer, GlobalModel, Globals, Light, Mesh, Model, PostProcessLocals, PostProcessPipeline, Renderer,
Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline, Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline, TerrainPipeline,
}, },
scene::{ scene::{
camera::{self, Camera, CameraMode}, camera::{self, Camera, CameraMode},
figure::{load_mesh, FigureColLights, FigureModelCache, FigureState}, figure::{load_mesh, FigureColLights, FigureModelCache, FigureModelEntry, FigureState},
LodData, LodData,
}, },
window::{Event, PressState}, window::{Event, PressState},
@ -46,13 +46,14 @@ impl ReadVol for VoidVol {
fn generate_mesh<'a>( fn generate_mesh<'a>(
greedy: &mut GreedyMesh<'a>, greedy: &mut GreedyMesh<'a>,
mesh: &mut Mesh<TerrainPipeline>,
segment: Segment, segment: Segment,
offset: Vec3<f32>, offset: Vec3<f32>,
) -> BoneMeshes { ) -> BoneMeshes {
let (opaque, _, /* shadow */ _, bounds) = let (opaque, _, /* shadow */ _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh( Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment, segment,
(greedy, offset, Vec3::one()), (greedy, mesh, offset, Vec3::one()),
); );
(opaque /* , shadow */, bounds) (opaque /* , shadow */, bounds)
} }
@ -77,7 +78,7 @@ pub struct Scene {
map_bounds: Vec2<f32>, map_bounds: Vec2<f32>,
col_lights: FigureColLights, col_lights: FigureColLights,
backdrop: Option<(FigureModel, FigureState<FixtureSkeleton>)>, backdrop: Option<(FigureModelEntry<1>, FigureState<FixtureSkeleton>)>,
figure_model_cache: FigureModelCache, figure_model_cache: FigureModelCache,
figure_state: FigureState<CharacterSkeleton>, figure_state: FigureState<CharacterSkeleton>,
@ -150,12 +151,20 @@ impl Scene {
backdrop: backdrop.map(|specifier| { backdrop: backdrop.map(|specifier| {
let mut state = FigureState::new(renderer, FixtureSkeleton::default()); let mut state = FigureState::new(renderer, FixtureSkeleton::default());
let mut greedy = FigureModel::make_greedy(); let mut greedy = FigureModel::make_greedy();
let mesh = load_mesh( let mut opaque_mesh = Mesh::new();
let (_opaque_mesh, (bounds, range)) = load_mesh(
specifier, specifier,
Vec3::new(-55.0, -49.5, -2.0), 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]; let mut buf = [Default::default(); anim::MAX_BONE_COUNT];
state.update( state.update(
renderer, renderer,
@ -315,7 +324,7 @@ impl Scene {
Rgba::broadcast(1.0), Rgba::broadcast(1.0),
scene_data.delta_time, scene_data.delta_time,
1.0, 1.0,
&model[0], &model,
0, 0,
true, true,
false, false,
@ -354,8 +363,8 @@ impl Scene {
.0; .0;
renderer.render_figure( renderer.render_figure(
&model[0], &model.models[0],
&self.col_lights.texture(), &self.col_lights.texture(model),
&self.data, &self.data,
self.figure_state.locals(), self.figure_state.locals(),
self.figure_state.bone_consts(), self.figure_state.bone_consts(),
@ -365,8 +374,8 @@ impl Scene {
if let Some((model, state)) = &self.backdrop { if let Some((model, state)) = &self.backdrop {
renderer.render_figure( renderer.render_figure(
model, &model.models[0],
&self.col_lights.texture(), &self.col_lights.texture(model),
&self.data, &self.data,
state.locals(), state.locals(),
state.bone_consts(), state.bone_consts(),

View File

@ -393,7 +393,7 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug>(
volume: <VolGrid2d<V> as SampleVol<Aabr<i32>>>::Sample, volume: <VolGrid2d<V> as SampleVol<Aabr<i32>>>::Sample,
max_texture_size: u16, max_texture_size: u16,
range: Aabb<i32>, range: Aabb<i32>,
sprite_models: &HashMap<(BlockKind, usize), Vec<SpriteData>>, sprite_data: &HashMap<(BlockKind, usize), Vec<SpriteData>>,
) -> MeshWorkerResponse { ) -> MeshWorkerResponse {
let (opaque_mesh, fluid_mesh, _shadow_mesh, (bounds, col_lights_info)) = let (opaque_mesh, fluid_mesh, _shadow_mesh, (bounds, col_lights_info)) =
volume.generate_mesh((range, Vec2::new(max_texture_size, max_texture_size))); volume.generate_mesh((range, Vec2::new(max_texture_size, max_texture_size)));
@ -424,7 +424,7 @@ fn mesh_worker<V: BaseVol<Vox = Block> + RectRasterableVol + ReadVol + Debug>(
let key = (block.kind(), variation); let key = (block.kind(), variation);
// NOTE: Safe bbecause we called sprite_config_for already. // NOTE: Safe bbecause we called sprite_config_for already.
// NOTE: Safe because 0 ≤ ori < 8 // NOTE: Safe because 0 ≤ ori < 8
let sprite_data = &sprite_models[&key][0]; let sprite_data = &sprite_data[&key][0];
let instance = SpriteInstance::new( let instance = SpriteInstance::new(
Mat4::identity() Mat4::identity()
.translated_3d(sprite_data.offset) .translated_3d(sprite_data.offset)
@ -484,7 +484,7 @@ pub struct Terrain<V: RectRasterableVol> {
mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>, mesh_todo: HashMap<Vec2<i32>, ChunkMeshState>,
// GPU data // GPU data
sprite_models: Arc<HashMap<(BlockKind, usize), Vec<SpriteData>>>, sprite_data: Arc<HashMap<(BlockKind, usize), Vec<SpriteData>>>,
sprite_col_lights: Texture<ColLightFmt>, sprite_col_lights: Texture<ColLightFmt>,
col_lights: Texture<ColLightFmt>, col_lights: Texture<ColLightFmt>,
waves: Texture, waves: Texture,
@ -513,6 +513,7 @@ impl<V: RectRasterableVol> Terrain<V> {
guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size)); guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
let mut greedy = GreedyMesh::new(max_size); let mut greedy = GreedyMesh::new(max_size);
let mut locals_buffer = [SpriteLocals::default(); 8]; 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<f32>| { let mut make_models = |(kind, variation), s, offset, lod_axes: Vec3<f32>| {
let scaled = [1.0, 0.8, 0.6, 0.4, 0.2]; let scaled = [1.0, 0.8, 0.6, 0.4, 0.2];
let model = assets::load_expect::<DotVoxData>(s); let model = assets::load_expect::<DotVoxData>(s);
@ -550,12 +551,21 @@ impl<V: RectRasterableVol> Terrain<V> {
lod_axes * lod_scale_orig lod_axes * lod_scale_orig
+ lod_axes.map(|e| if e == 0.0 { 1.0 } else { 0.0 }) + lod_axes.map(|e| if e == 0.0 { 1.0 } else { 0.0 })
}; };
let opaque_model = // Mesh generation exclusively acts using side effects; it has no
Meshable::<SpritePipeline, &mut GreedyMesh>::generate_mesh( // interesting return value, but updates the mesh.
Segment::from(model.as_ref()).scaled_by(lod_scale), let mut opaque_mesh = Mesh::new();
(&mut greedy, wind_sway >= 0.4 && lod_scale_orig == 1.0), Meshable::<SpritePipeline, &mut GreedyMesh>::generate_mesh(
) Segment::from(model.as_ref()).scaled_by(lod_scale),
.0; (
&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_scale = Vec3::one() / lod_scale;
let sprite_mat: Mat4<f32> = sprite_mat * Mat4::scaling_3d(sprite_scale); let sprite_mat: Mat4<f32> = sprite_mat * Mat4::scaling_3d(sprite_scale);
locals_buffer locals_buffer
@ -569,8 +579,8 @@ impl<V: RectRasterableVol> Terrain<V> {
}); });
SpriteData { SpriteData {
/* vertex_range */ model,
offset, offset,
model: renderer.create_model(&opaque_model).unwrap(),
locals: renderer locals: renderer
.create_consts(&locals_buffer) .create_consts(&locals_buffer)
.expect("Failed to upload sprite locals to the GPU!"), .expect("Failed to upload sprite locals to the GPU!"),
@ -580,7 +590,7 @@ impl<V: RectRasterableVol> Terrain<V> {
) )
}; };
let sprite_models: HashMap<(BlockKind, usize), _> = vec![ let sprite_data: HashMap<(BlockKind, usize), _> = vec![
// Windows // Windows
make_models( make_models(
(BlockKind::Window1, 0), (BlockKind::Window1, 0),
@ -2370,6 +2380,7 @@ impl<V: RectRasterableVol> Terrain<V> {
.collect(); .collect();
let sprite_col_lights = ShadowPipeline::create_col_lights(renderer, greedy.finalize()) let sprite_col_lights = ShadowPipeline::create_col_lights(renderer, greedy.finalize())
.expect("Failed to upload sprite color and light data to the GPU!"); .expect("Failed to upload sprite color and light data to the GPU!");
Self { Self {
atlas, atlas,
chunks: HashMap::default(), chunks: HashMap::default(),
@ -2377,7 +2388,7 @@ impl<V: RectRasterableVol> Terrain<V> {
mesh_send_tmp: send, mesh_send_tmp: send,
mesh_recv: recv, mesh_recv: recv,
mesh_todo: HashMap::default(), mesh_todo: HashMap::default(),
sprite_models: Arc::new(sprite_models), sprite_data: Arc::new(sprite_data),
sprite_col_lights, sprite_col_lights,
waves: renderer waves: renderer
.create_texture( .create_texture(
@ -2624,9 +2635,9 @@ impl<V: RectRasterableVol> Terrain<V> {
// Queue the worker thread. // Queue the worker thread.
let started_tick = todo.started_tick; 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 || { scene_data.thread_pool.execute(move || {
let sprite_models = sprite_models; let sprite_data = sprite_data;
let _ = send.send(mesh_worker( let _ = send.send(mesh_worker(
pos, pos,
(min_z as f32, max_z as f32), (min_z as f32, max_z as f32),
@ -2634,7 +2645,7 @@ impl<V: RectRasterableVol> Terrain<V> {
volume, volume,
max_texture_size, max_texture_size,
aabb, aabb,
&sprite_models, &sprite_data,
)); ));
}); });
todo.active_worker = Some(todo.started_tick); todo.active_worker = Some(todo.started_tick);
@ -3067,15 +3078,15 @@ impl<V: RectRasterableVol> Terrain<V> {
&& dist_sqrd <= chunk_mag && dist_sqrd <= chunk_mag
|| dist_sqrd < sprite_high_detail_distance.powf(2.0) || 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) { } 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) { } 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) { } else if dist_sqrd < sprite_low_detail_distance.powf(2.0) {
&self.sprite_models[&kind][3] &self.sprite_data[&kind][3]
} else { } else {
&self.sprite_models[&kind][4] &self.sprite_data[&kind][4]
}; };
renderer.render_sprites( renderer.render_sprites(
model, model,

View File

@ -659,12 +659,7 @@ pub enum AudioOutput {
} }
impl AudioOutput { impl AudioOutput {
pub fn is_enabled(&self) -> bool { pub fn is_enabled(&self) -> bool { !matches!(self, Self::Off) }
match self {
Self::Off => false,
_ => true,
}
}
} }
/// `AudioSettings` controls the volume of different audio subsystems and which /// `AudioSettings` controls the volume of different audio subsystems and which
/// device is used. /// device is used.

View File

@ -30,23 +30,19 @@ impl Event {
} }
pub fn is_keyboard_or_mouse(&self) -> bool { pub fn is_keyboard_or_mouse(&self) -> bool {
match self.0 { matches!(self.0,
Input::Press(_) Input::Press(_)
| Input::Release(_) | Input::Release(_)
| Input::Motion(_) | Input::Motion(_)
| Input::Touch(_) | Input::Touch(_)
| Input::Text(_) => true, | Input::Text(_))
_ => false,
}
} }
pub fn is_keyboard(&self) -> bool { pub fn is_keyboard(&self) -> bool {
match self.0 { matches!(self.0,
Input::Press(Button::Keyboard(_)) Input::Press(Button::Keyboard(_))
| Input::Release(Button::Keyboard(_)) | Input::Release(Button::Keyboard(_))
| Input::Text(_) => true, | Input::Text(_))
_ => false,
}
} }
pub fn new_resize(dims: Vec2<f64>) -> Self { Self(Input::Resize(dims.x, dims.y)) } pub fn new_resize(dims: Vec2<f64>) -> Self { Self(Input::Resize(dims.x, dims.y)) }

View File

@ -43,13 +43,12 @@ use conrod_core::{
widget::{self, id::Generator}, widget::{self, id::Generator},
Rect, Scalar, UiBuilder, UiCell, Rect, Scalar, UiBuilder, UiCell,
}; };
use core::{convert::TryInto, f32, f64, ops::Range};
use graphic::{Rotation, TexId}; use graphic::{Rotation, TexId};
use hashbrown::hash_map::Entry; use hashbrown::hash_map::Entry;
use std::{ use std::{
f32, f64,
fs::File, fs::File,
io::{BufReader, Read}, io::{BufReader, Read},
ops::Range,
sync::Arc, sync::Arc,
time::Duration, time::Duration,
}; };
@ -67,7 +66,7 @@ enum DrawKind {
Plain, Plain,
} }
enum DrawCommand { enum DrawCommand {
Draw { kind: DrawKind, verts: Range<usize> }, Draw { kind: DrawKind, verts: Range<u32> },
Scissor(Aabr<u16>), Scissor(Aabr<u16>),
WorldPos(Option<usize>), WorldPos(Option<usize>),
} }
@ -75,14 +74,28 @@ impl DrawCommand {
fn image(verts: Range<usize>, id: TexId) -> DrawCommand { fn image(verts: Range<usize>, id: TexId) -> DrawCommand {
DrawCommand::Draw { DrawCommand::Draw {
kind: DrawKind::Image(id), 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<usize>) -> DrawCommand { fn plain(verts: Range<usize>) -> DrawCommand {
DrawCommand::Draw { DrawCommand::Draw {
kind: DrawKind::Plain, 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(), DrawKind::Plain => self.cache.glyph_cache_tex(),
}; };
let model = self.model.submodel(verts.clone()); 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);
}, },
} }
} }

View File

@ -3,7 +3,6 @@
#![allow(clippy::option_map_unit_fn)] #![allow(clippy::option_map_unit_fn)]
#![feature( #![feature(
arbitrary_enum_discriminant, arbitrary_enum_discriminant,
const_if_match,
const_generics, const_generics,
const_panic, const_panic,
label_break_value, label_break_value,

View File

@ -120,29 +120,11 @@ pub enum RiverKind {
} }
impl RiverKind { impl RiverKind {
pub fn is_ocean(&self) -> bool { pub fn is_ocean(&self) -> bool { matches!(*self, RiverKind::Ocean) }
if let RiverKind::Ocean = *self {
true
} else {
false
}
}
pub fn is_river(&self) -> bool { pub fn is_river(&self) -> bool { matches!(*self, RiverKind::River { .. }) }
if let RiverKind::River { .. } = *self {
true
} else {
false
}
}
pub fn is_lake(&self) -> bool { pub fn is_lake(&self) -> bool { matches!(*self, RiverKind::Lake { .. }) }
if let RiverKind::Lake { .. } = *self {
true
} else {
false
}
}
} }
impl PartialOrd for RiverKind { impl PartialOrd for RiverKind {

View File

@ -170,9 +170,7 @@ pub fn sample_pos(
} }
}); });
let downhill_wpos = downhill let downhill_wpos = downhill.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
.map(|downhill_pos| downhill_pos)
.unwrap_or(wpos + TerrainChunkSize::RECT_SIZE.map(|e| e as i32));
let alt = if is_basement { basement } else { alt }; let alt = if is_basement { basement } else { alt };
let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64; let true_water_alt = (alt.max(water_alt) as f64 - focus.z) / gain as f64;

View File

@ -93,21 +93,9 @@ pub enum StoreyFill {
} }
impl StoreyFill { impl StoreyFill {
fn has_lower(&self) -> bool { fn has_lower(&self) -> bool { matches!(self, StoreyFill::All) }
if let StoreyFill::All = self {
true
} else {
false
}
}
fn has_upper(&self) -> bool { fn has_upper(&self) -> bool { !matches!(self, StoreyFill::None) }
if let StoreyFill::None = self {
false
} else {
true
}
}
} }
#[derive(Copy, Clone)] #[derive(Copy, Clone)]

View File

@ -236,10 +236,7 @@ impl Settlement {
let origin = dir.map(|e| (e * 100.0) as i32); let origin = dir.map(|e| (e * 100.0) as i32);
let origin = self let origin = self
.land .land
.find_tile_near(origin, |plot| match plot { .find_tile_near(origin, |plot| matches!(plot, Some(&Plot::Field { .. })))
Some(&Plot::Field { .. }) => true,
_ => false,
})
.unwrap(); .unwrap();
if let Some(path) = self.town.as_ref().and_then(|town| { if let Some(path) = self.town.as_ref().and_then(|town| {
@ -520,7 +517,7 @@ impl Settlement {
.land .land
.get_at_block(wpos - self.origin) .get_at_block(wpos - self.origin)
.plot .plot
.map(|p| if let Plot::Hazard = p { true } else { false }) .map(|p| matches!(p, Plot::Hazard))
.unwrap_or(true), .unwrap_or(true),
..SpawnRules::default() ..SpawnRules::default()
} }