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
pub fn is_friendly_to_players(&self) -> bool {
match self {
Alignment::Npc | Alignment::Tame | Alignment::Owned(_) => true,
_ => false,
}
matches!(self, Alignment::Npc | Alignment::Tame | Alignment::Owned(_))
}
}
@ -129,19 +126,9 @@ pub enum Activity {
}
impl Activity {
pub fn is_follow(&self) -> bool {
match self {
Activity::Follow { .. } => true,
_ => false,
}
}
pub fn is_follow(&self) -> bool { matches!(self, Activity::Follow { .. }) }
pub fn is_attack(&self) -> bool {
match self {
Activity::Attack { .. } => true,
_ => false,
}
}
pub fn is_attack(&self) -> bool { matches!(self, Activity::Attack { .. }) }
}
impl Default for Activity {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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

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 {
match self.client_state {
ClientState::Registered | ClientState::Spectator | ClientState::Character => true,
_ => false,
}
matches!(
self.client_state,
ClientState::Registered | ClientState::Spectator | ClientState::Character
)
}
pub fn is_ingame(&self) -> bool {
match self.client_state {
ClientState::Spectator | ClientState::Character => true,
_ => false,
}
matches!(
self.client_state,
ClientState::Spectator | ClientState::Character
)
}
pub fn allow_state(&mut self, new_state: ClientState) {

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
/// the kill. Experience given is equal to the level of the entity that was
/// killed times 10.
// NOTE: Clippy incorrectly warns about a needless collect here because it does not
// understand that the pet count (which is computed during the first iteration over the
// members in range) is actually used by the second iteration over the members in range;
// since we have no way of knowing the pet count before the first loop finishes, we
// definitely need at least two loops. Then (currently) our only options are to store
// the member list in temporary space (e.g. by collecting to a vector), or to repeat
// the loop; but repeating the loop would currently be very inefficient since it has to
// rescan every entity on the server again.
#[allow(clippy::needless_collect)]
pub fn handle_destroy(server: &mut Server, entity: EcsEntity, cause: HealthSource) {
let state = server.state_mut();

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::same_item_push)] // TODO: Pending review in #587
pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::InventoryManip) {
let state = server.state_mut();
let mut dropped_items = Vec::new();
@ -336,6 +337,7 @@ pub fn handle_inventory(server: &mut Server, entity: EcsEntity, manip: comp::Inv
.and_then(|ldt| slot::loadout_remove(slot, ldt)),
};
// FIXME: We should really require the drop and write to be atomic!
if let (Some(item), Some(pos)) =
(item, state.ecs().read_storage::<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 craft_result = recipe_book.get(&recipe).and_then(|r| r.perform(inv).ok());
// FIXME: We should really require the drop and write to be atomic!
if craft_result.is_some() {
let _ = state.ecs().write_storage().insert(
entity,

View File

@ -214,7 +214,6 @@ impl StateExt for State {
// Notify clients of a player list update
let client_uid = self
.read_component_cloned::<Uid>(entity)
.map(|u| u)
.expect("Client doesn't have a Uid!!!");
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))
.cloned()
.collect::<Vec<_>>();
if outcomes.len() > 0 {
if !outcomes.is_empty() {
client.notify(ServerMsg::Outcomes(outcomes));
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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> {
Ok(Self {
ibuf: factory
.create_buffer(len, Role::Vertex, Usage::Dynamic, Bind::TRANSFER_DST)
.create_buffer(len, Role::Vertex, Usage::Dynamic, Bind::empty())
.map_err(RenderError::BufferCreationError)?,
})
}

View File

@ -1,5 +1,5 @@
use super::Pipeline;
use std::iter::FromIterator;
use core::{iter::FromIterator, ops::Range};
/// A `Vec`-based mesh structure used to store mesh data on the CPU.
pub struct Mesh<P: Pipeline> {
@ -69,6 +69,11 @@ impl<P: Pipeline> Mesh<P> {
}
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> {

View File

@ -244,7 +244,7 @@ impl core::convert::TryFrom<ShadowMode> for ShadowMapMode {
}
impl ShadowMode {
pub fn is_map(&self) -> bool { if let Self::Map(_) = self { true } else { false } }
pub fn is_map(&self) -> bool { matches!(self, Self::Map(_)) }
}
/// Render modes

View File

@ -22,6 +22,15 @@ impl<P: Pipeline> Model<P> {
}
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.
@ -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
/// renderer.
pub fn submodel(&self, range: Range<usize>) -> Model<P> {
pub fn submodel(&self, vertex_range: Range<u32>) -> Model<P> {
Model {
vbuf: self.vbuf.clone(),
vertex_range: range.start as u32..range.end as u32,
vertex_range,
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1002,7 +1002,7 @@ impl Renderer {
pub fn render_figure(
&mut self,
model: &figure::FigureModel,
_col_lights: &Texture<ColLightFmt>,
col_lights: &Texture<ColLightFmt>,
global: &GlobalModel,
locals: &Consts<figure::Locals>,
bones: &Consts<figure::BoneData>,
@ -1026,7 +1026,6 @@ impl Renderer {
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
)
};
let col_lights = &model.col_lights;
let model = &model.opaque;
self.encoder.draw(
@ -1087,10 +1086,7 @@ impl Renderer {
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
)
};
let col_lights = &model.col_lights;
let model = &model.opaque;
// let atlas_model = &model.opaque;
// let model = &model.shadow;
self.encoder.draw(
&gfx::Slice {
@ -1103,9 +1099,7 @@ impl Renderer {
&self.player_shadow_pipeline.pso,
&figure::pipe::Data {
vbuf: model.vbuf.clone(),
// abuf: atlas_model.vbuf.clone(),
col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()),
// col_lights: (lod.horizon.srv.clone(), lod.horizon.sampler.clone()),
locals: locals.buf.clone(),
globals: global.globals.buf.clone(),
bones: bones.buf.clone(),
@ -1127,7 +1121,7 @@ impl Renderer {
pub fn render_player(
&mut self,
model: &figure::FigureModel,
_col_lights: &Texture<ColLightFmt>,
col_lights: &Texture<ColLightFmt>,
global: &GlobalModel,
locals: &Consts<figure::Locals>,
bones: &Consts<figure::BoneData>,
@ -1151,7 +1145,6 @@ impl Renderer {
(self.noise_tex.srv.clone(), self.noise_tex.sampler.clone()),
)
};
let col_lights = &model.col_lights;
let model = &model.opaque;
self.encoder.draw(
@ -1476,6 +1469,10 @@ impl Renderer {
ibuf: instances.ibuf.clone(),
col_lights: (col_lights.srv.clone(), col_lights.sampler.clone()),
terrain_locals: terrain_locals.buf.clone(),
// NOTE: It would be nice if this wasn't needed and we could use a constant buffer
// offset into the sprite data. Hopefully, when we switch to wgpu we can do this,
// as it offers the exact API we want (the equivalent can be done in OpenGL using
// glBindBufferOffset).
locals: locals.buf.clone(),
globals: global.globals.buf.clone(),
lights: global.lights.buf.clone(),
@ -1581,7 +1578,7 @@ impl Renderer {
/// Queue the rendering of the provided UI element in the upcoming frame.
pub fn render_ui_element<F: gfx::format::Formatted<View = [f32; 4]>>(
&mut self,
model: &Model<ui::UiPipeline>,
model: Model<ui::UiPipeline>,
tex: &Texture<F>,
scissor: Aabr<u16>,
globals: &Consts<Globals>,
@ -1594,15 +1591,15 @@ impl Renderer {
let Aabr { min, max } = scissor;
self.encoder.draw(
&gfx::Slice {
start: model.vertex_range().start,
end: model.vertex_range().end,
start: model.vertex_range.start,
end: model.vertex_range.end,
base_vertex: 0,
instances: None,
buffer: gfx::IndexBuffer::Auto,
},
&self.ui_pipeline.pso,
&ui::pipe::Data {
vbuf: model.vbuf.clone(),
vbuf: model.vbuf,
scissor: gfx::Rect {
x: min.x,
y: min.y,

View File

@ -1,7 +1,7 @@
use super::load::*;
use super::{load::*, FigureModelEntry};
use crate::{
mesh::{greedy::GreedyMesh, Meshable},
render::{BoneMeshes, FigureModel, FigurePipeline, Mesh, Renderer},
render::{BoneMeshes, FigureModel, FigurePipeline, Mesh, Renderer, TerrainPipeline},
scene::camera::CameraMode,
};
use anim::Skeleton;
@ -22,7 +22,7 @@ use core::convert::TryInto;
use hashbrown::{hash_map::Entry, HashMap};
use vek::*;
pub type FigureModelEntry = [FigureModel; 3];
pub type FigureModelEntryLod = FigureModelEntry<3>;
#[derive(Eq, Hash, PartialEq)]
struct FigureKey {
@ -198,7 +198,7 @@ pub struct FigureModelCache<Skel = anim::character::CharacterSkeleton>
where
Skel: Skeleton,
{
models: HashMap<FigureKey, ((FigureModelEntry, Skel::Attr), u64)>,
models: HashMap<FigureKey, ((FigureModelEntryLod, Skel::Attr), u64)>,
manifest_indicator: ReloadIndicator,
}
@ -980,7 +980,7 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
tick: u64,
camera_mode: CameraMode,
character_state: Option<&CharacterState>,
) -> &(FigureModelEntry, Skel::Attr)
) -> &(FigureModelEntryLod, Skel::Attr)
where
for<'a> &'a common::comp::Body: std::convert::TryInto<Skel::Attr>,
Skel::Attr: Default,
@ -1011,78 +1011,137 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
.unwrap_or_else(<Skel::Attr as Default>::default);
let manifest_indicator = &mut self.manifest_indicator;
let mut make_model =
|generate_mesh: for<'a> fn(&mut GreedyMesh<'a>, _, _) -> _| {
let mut greedy = FigureModel::make_greedy();
let mut opaque = Mesh::new();
let mut figure_bounds = Aabb {
min: Vec3::zero(),
max: Vec3::zero(),
let mut opaque = Mesh::<TerrainPipeline>::new();
// Choose the most conservative bounds for any LOD model.
let mut figure_bounds = anim::vek::Aabb {
min: anim::vek::Vec3::zero(),
max: anim::vek::Vec3::zero(),
};
// Meshes all bone models for this figure using the given mesh generation
// function, attaching it to the current greedy mesher and opaque vertex
// list. Returns the vertex bounds of the meshed model within the opaque
// mesh.
let mut make_model = |generate_mesh: for<'a, 'b> fn(
&mut GreedyMesh<'a>,
&'b mut _,
_,
_,
)
-> _| {
let vertex_start = opaque.vertices().len();
let meshes =
Self::bone_meshes(key, manifest_indicator, |segment, offset| {
generate_mesh(&mut greedy, segment, offset)
})
generate_mesh(&mut greedy, &mut opaque, segment, offset)
});
meshes
.iter()
.enumerate()
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i, bm)))
.for_each(|(i, (opaque_mesh, bounds))| {
// NOTE: Cast to u8 is safe because i <= 16.
.filter_map(|(i, bm)| bm.as_ref().map(|bm| (i as u8, bm.clone())))
.for_each(|(i, (_opaque_mesh, (bounds, vertex_range)))| {
// Update the bone index for all vertices that belong ot this
// model.
opaque
.push_mesh_map(opaque_mesh, |vert| vert.with_bone_idx(i as u8));
figure_bounds.expand_to_contain(*bounds);
.iter_mut(vertex_range)
.for_each(|vert| {
vert.set_bone_idx(i);
});
col_lights
.create_figure(renderer, greedy, (opaque, figure_bounds))
.unwrap()
// 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);
});
// NOTE: vertex_start and vertex_end *should* fit in a u32, by the
// following logic:
//
// Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8.
// This uses at most 24 bits to store every vertex exactly once.
// Greedy meshing can store each vertex in up to 3 quads, we have 3
// greedy models, and we store 1.5x the vertex count, so the maximum
// total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding
// up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a
// u32.
//
// (We could also, though we prefer not to, reason backwards from the
// maximum figure texture size of 2^15 × 2^15, also fits in a u32; we
// can also see that, since we can have at most one texture entry per
// vertex, any texture atlas of size 2^14 × 2^14 or higher should be
// able to store data for any figure. So the only reason we would fail
// here would be if the user's computer could not store a texture large
// enough to fit all the LOD models for the figure, not for fundamental
// reasonS related to fitting in a u32).
//
// Therefore, these casts are safe.
vertex_start as u32..opaque.vertices().len() as u32
};
fn generate_mesh<'a>(
greedy: &mut GreedyMesh<'a>,
opaque_mesh: &mut Mesh<TerrainPipeline>,
segment: Segment,
offset: Vec3<f32>,
) -> BoneMeshes {
let (opaque, _, _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment,
(greedy, offset, Vec3::one()),
(greedy, opaque_mesh, offset, Vec3::one()),
);
(opaque, bounds)
}
fn generate_mesh_lod_mid<'a>(
greedy: &mut GreedyMesh<'a>,
opaque_mesh: &mut Mesh<TerrainPipeline>,
segment: Segment,
offset: Vec3<f32>,
) -> BoneMeshes {
let lod_scale = Vec3::broadcast(0.6);
let lod_scale = 0.6;
let (opaque, _, _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment.scaled_by(lod_scale),
(greedy, offset * lod_scale, Vec3::one() / lod_scale),
segment.scaled_by(Vec3::broadcast(lod_scale)),
(
greedy,
opaque_mesh,
offset * lod_scale,
Vec3::one() / lod_scale,
),
);
(opaque, bounds)
}
fn generate_mesh_lod_low<'a>(
greedy: &mut GreedyMesh<'a>,
opaque_mesh: &mut Mesh<TerrainPipeline>,
segment: Segment,
offset: Vec3<f32>,
) -> BoneMeshes {
let lod_scale = Vec3::broadcast(0.3);
let segment = segment.scaled_by(lod_scale);
let lod_scale = 0.3;
let (opaque, _, _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment,
(greedy, offset * lod_scale, Vec3::one() / lod_scale),
segment.scaled_by(Vec3::broadcast(lod_scale)),
(
greedy,
opaque_mesh,
offset * lod_scale,
Vec3::one() / lod_scale,
),
);
(opaque, bounds)
}
(
[
let models = [
make_model(generate_mesh),
make_model(generate_mesh_lod_mid),
make_model(generate_mesh_lod_low),
],
];
(
col_lights
.create_figure(renderer, greedy, (opaque, figure_bounds), models)
.expect("Failed to upload figure data to the GPU!"),
skeleton_attr,
)
};
@ -1100,14 +1159,12 @@ impl<Skel: Skeleton> FigureModelCache<Skel> {
}
// TODO: Don't hard-code this.
if tick % 60 == 0 {
self.models.retain(|_, ((models, _), last_used)| {
self.models.retain(|_, ((model_entry, _), last_used)| {
// Wait about a minute at 60 fps before invalidating old models.
let delta = 60 * 60;
let alive = *last_used + delta > tick;
if !alive {
models.iter().for_each(|model| {
col_lights.atlas.deallocate(model.allocation.id);
});
col_lights.atlas.deallocate(model_entry.allocation.id);
}
alive
});

View File

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

View File

@ -8,8 +8,8 @@ use crate::{
ecs::comp::Interpolated,
mesh::greedy::GreedyMesh,
render::{
BoneMeshes, ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, GlobalModel,
RenderError, Renderer, ShadowPipeline, Texture,
ColLightFmt, Consts, FigureBoneData, FigureLocals, FigureModel, GlobalModel, Mesh,
RenderError, Renderer, ShadowPipeline, TerrainPipeline, Texture,
},
scene::{
camera::{Camera, CameraMode, Dependents},
@ -36,8 +36,9 @@ use common::{
};
use core::{
borrow::Borrow,
convert::TryFrom,
hash::Hash,
ops::{Deref, DerefMut},
ops::{Deref, DerefMut, Range},
};
use guillotiere::AtlasAllocator;
use hashbrown::HashMap;
@ -51,6 +52,33 @@ const MOVING_THRESHOLD_SQR: f32 = MOVING_THRESHOLD * MOVING_THRESHOLD;
/// camera data, fiigure LOD render distance.
pub type CameraData<'a> = (&'a Camera, f32);
/// Enough data to render a figure model.
pub type FigureModelRef<'a> = (
&'a Consts<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 {
character_states: HashMap<EcsEntity, FigureState<CharacterSkeleton>>,
quadruped_small_states: HashMap<EcsEntity, FigureState<QuadrupedSmallSkeleton>>,
@ -1018,7 +1046,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1119,7 +1147,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1220,7 +1248,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1319,7 +1347,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1415,7 +1443,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1500,7 +1528,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1581,7 +1609,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1663,7 +1691,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1748,7 +1776,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1833,7 +1861,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -1929,7 +1957,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -2011,7 +2039,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
in_frustum,
is_player,
@ -2043,7 +2071,7 @@ impl FigureMgr {
col,
dt,
state_animation_rate,
&model[0],
&model,
lpindex,
true,
is_player,
@ -2159,14 +2187,7 @@ impl FigureMgr {
figure_lod_render_distance,
|state| state.visible(),
) {
renderer.render_figure(
model,
&col_lights.col_lights,
global,
locals,
bone_consts,
lod,
);
renderer.render_figure(model, &col_lights, global, locals, bone_consts, lod);
}
}
}
@ -2215,17 +2236,10 @@ impl FigureMgr {
figure_lod_render_distance,
|state| state.visible(),
) {
renderer.render_player(
model,
&col_lights.col_lights,
global,
locals,
bone_consts,
lod,
);
renderer.render_player(model, &col_lights, global, locals, bone_consts, lod);
renderer.render_player_shadow(
model,
&col_lights.col_lights,
&col_lights,
global,
bone_consts,
lod,
@ -2246,16 +2260,10 @@ impl FigureMgr {
body: &Body,
loadout: Option<&Loadout>,
is_player: bool,
// is_shadow: bool,
pos: vek::Vec3<f32>,
figure_lod_render_distance: f32,
filter_state: impl Fn(&FigureStateMeta) -> bool,
) -> Option<(
&Consts<FigureLocals>,
&Consts<FigureBoneData>,
&FigureModel,
&FigureColLights,
)> {
) -> Option<FigureModelRef> {
let player_camera_mode = if is_player {
camera.get_mode()
} else {
@ -2297,7 +2305,7 @@ impl FigureMgr {
},
} = self;
let col_lights = &mut *col_lights_;
if let Some((locals, bone_consts, model)) = match body {
if let Some((locals, bone_consts, model_entry)) = match body {
Body::Humanoid(_) => character_states
.get(&entity)
.filter(|state| filter_state(&*state))
@ -2563,14 +2571,14 @@ impl FigureMgr {
let figure_mid_detail_distance = figure_lod_render_distance * 0.5;
let model = if pos.distance_squared(cam_pos) > figure_low_detail_distance.powf(2.0) {
&model[2]
&model_entry.models[2]
} else if pos.distance_squared(cam_pos) > figure_mid_detail_distance.powf(2.0) {
&model[1]
&model_entry.models[1]
} else {
&model[0]
&model_entry.models[0]
};
Some((locals, bone_consts, model, &*col_lights_))
Some((locals, bone_consts, model, col_lights_.texture(model_entry)))
} else {
// trace!("Body has no saved figure");
None
@ -2584,24 +2592,39 @@ impl FigureMgr {
pub struct FigureColLights {
atlas: AtlasAllocator,
col_lights: Texture<ColLightFmt>,
// col_lights: Texture<ColLightFmt>,
}
impl FigureColLights {
pub fn new(renderer: &mut Renderer) -> Self {
let (atlas, col_lights) =
Self::make_atlas(renderer).expect("Failed to create texture atlas for figures");
Self { atlas, col_lights }
let atlas = Self::make_atlas(renderer).expect("Failed to create texture atlas for figures");
Self {
atlas, /* col_lights, */
}
}
pub fn texture(&self) -> &Texture<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,
renderer: &mut Renderer,
greedy: GreedyMesh<'a>,
(opaque, bounds): BoneMeshes,
) -> Result<FigureModel, RenderError> {
(opaque, bounds): (Mesh<TerrainPipeline>, math::Aabb<f32>),
vertex_range: [Range<u32>; N],
) -> Result<FigureModelEntry<N>, RenderError> {
let (tex, tex_size) = greedy.finalize();
let atlas = &mut self.atlas;
let allocation = atlas
@ -2609,21 +2632,32 @@ impl FigureColLights {
i32::from(tex_size.x),
i32::from(tex_size.y),
))
.expect("Not yet implemented: allocate new atlas on allocation faillure.");
.expect("Not yet implemented: allocate new atlas on allocation failure.");
let col_lights = ShadowPipeline::create_col_lights(renderer, (tex, tex_size))?;
let model_len = u32::try_from(opaque.vertices().len())
.expect("The model size for this figure does not fit in a u32!");
let model = renderer.create_model(&opaque)?;
Ok(FigureModel {
bounds,
opaque: renderer.create_model(&opaque)?,
// shadow: renderer.create_model(&shadow)?,
Ok(FigureModelEntry {
_bounds: bounds,
models: vertex_range.map(|range| {
assert!(
range.start <= range.end && range.end <= model_len,
"The provided vertex range for figure mesh {:?} does not fit in the model, \
which is of size {:?}!",
range,
model_len
);
FigureModel {
opaque: model.submodel(range),
}
}),
col_lights,
allocation,
})
}
fn make_atlas(
renderer: &mut Renderer,
) -> Result<(AtlasAllocator, Texture<ColLightFmt>), RenderError> {
fn make_atlas(renderer: &mut Renderer) -> Result<AtlasAllocator, RenderError> {
let max_texture_size = renderer.max_texture_size();
let atlas_size =
guillotiere::Size::new(i32::from(max_texture_size), i32::from(max_texture_size));
@ -2633,7 +2667,11 @@ impl FigureColLights {
large_size_threshold: 256,
..guillotiere::AllocatorOptions::default()
});
let texture = renderer.create_texture_raw(
// TODO: Consider using a single texture atlas to store all figures, much like
// we do for terrain chunks. We previoosly avoided this due to
// perceived performance degradation for the figure use case, but with a
// smaller atlas size this may be less likely.
/* let texture = renderer.create_texture_raw(
gfx::texture::Kind::D2(
max_texture_size,
max_texture_size,
@ -2649,7 +2687,8 @@ impl FigureColLights {
gfx::texture::WrapMode::Clamp,
),
)?;
Ok((atlas, texture))
Ok((atlas, texture)) */
Ok(atlas)
}
}
@ -2714,7 +2753,7 @@ impl<S: Skeleton> FigureState<S> {
}
#[allow(clippy::too_many_arguments)] // TODO: Pending review in #587
pub fn update(
pub fn update<const N: usize>(
&mut self,
renderer: &mut Renderer,
pos: anim::vek::Vec3<f32>,
@ -2723,7 +2762,7 @@ impl<S: Skeleton> FigureState<S> {
col: vek::Rgba<f32>,
dt: f32,
state_animation_rate: f32,
model: &FigureModel,
model: &FigureModelEntry<N>,
_lpindex: u8,
_visible: 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
// dimension, but we're not, so we double it and use size() instead of
// half_size()).
let radius = model.bounds.half_size().reduce_partial_max();
let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius);
/* let radius = vek::Extent3::<f32>::from(model.bounds.half_size()).reduce_partial_max();
let _bounds = BoundingSphere::new(pos.into_array(), scale * 0.8 * radius); */
self.last_ori = vek::Lerp::lerp(self.last_ori, ori, 15.0 * dt);

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;
Quad::new(
Vertex::new(Vec2::new(x + 0, y + 0).map(transform)),
Vertex::new(Vec2::new(x + 1, y + 0).map(transform)),
Vertex::new(Vec2::new(x, y).map(transform)),
Vertex::new(Vec2::new(x + 1, y).map(transform)),
Vertex::new(Vec2::new(x + 1, y + 1).map(transform)),
Vertex::new(Vec2::new(x + 0, y + 1).map(transform)),
Vertex::new(Vec2::new(x, y + 1).map(transform)),
)
.rotated_by(if (x > detail as i32 / 2) ^ (y > detail as i32 / 2) {
0

View File

@ -994,7 +994,7 @@ impl Scene {
let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
// would instead have this as an extension.
if renderer.render_mode().shadow.is_map() && (is_daylight || light_data.1.len() > 0) {
if renderer.render_mode().shadow.is_map() && (is_daylight || !light_data.1.is_empty()) {
if is_daylight {
// Set up shadow mapping.
renderer.start_shadows();

View File

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

View File

@ -2,12 +2,12 @@ use crate::{
mesh::{greedy::GreedyMesh, Meshable},
render::{
create_pp_mesh, create_skybox_mesh, BoneMeshes, Consts, FigureModel, FigurePipeline,
GlobalModel, Globals, Light, Model, PostProcessLocals, PostProcessPipeline, Renderer,
Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline,
GlobalModel, Globals, Light, Mesh, Model, PostProcessLocals, PostProcessPipeline, Renderer,
Shadow, ShadowLocals, SkyboxLocals, SkyboxPipeline, TerrainPipeline,
},
scene::{
camera::{self, Camera, CameraMode},
figure::{load_mesh, FigureColLights, FigureModelCache, FigureState},
figure::{load_mesh, FigureColLights, FigureModelCache, FigureModelEntry, FigureState},
LodData,
},
window::{Event, PressState},
@ -46,13 +46,14 @@ impl ReadVol for VoidVol {
fn generate_mesh<'a>(
greedy: &mut GreedyMesh<'a>,
mesh: &mut Mesh<TerrainPipeline>,
segment: Segment,
offset: Vec3<f32>,
) -> BoneMeshes {
let (opaque, _, /* shadow */ _, bounds) =
Meshable::<FigurePipeline, &mut GreedyMesh>::generate_mesh(
segment,
(greedy, offset, Vec3::one()),
(greedy, mesh, offset, Vec3::one()),
);
(opaque /* , shadow */, bounds)
}
@ -77,7 +78,7 @@ pub struct Scene {
map_bounds: Vec2<f32>,
col_lights: FigureColLights,
backdrop: Option<(FigureModel, FigureState<FixtureSkeleton>)>,
backdrop: Option<(FigureModelEntry<1>, FigureState<FixtureSkeleton>)>,
figure_model_cache: FigureModelCache,
figure_state: FigureState<CharacterSkeleton>,
@ -150,12 +151,20 @@ impl Scene {
backdrop: backdrop.map(|specifier| {
let mut state = FigureState::new(renderer, FixtureSkeleton::default());
let mut greedy = FigureModel::make_greedy();
let mesh = load_mesh(
let mut opaque_mesh = Mesh::new();
let (_opaque_mesh, (bounds, range)) = load_mesh(
specifier,
Vec3::new(-55.0, -49.5, -2.0),
|segment, offset| generate_mesh(&mut greedy, segment, offset),
|segment, offset| generate_mesh(&mut greedy, &mut opaque_mesh, segment, offset),
);
let model = col_lights.create_figure(renderer, greedy, mesh).unwrap();
// NOTE: Since MagicaVoxel sizes are limited to 256 × 256 × 256, and there are
// at most 3 meshed vertices per unique vertex, we know the
// total size is bounded by 2^24 * 3 * 1.5 which is bounded by
// 2^27, which fits in a u32.
let range = range.start as u32..range.end as u32;
let model = col_lights
.create_figure(renderer, greedy, (opaque_mesh, bounds), [range])
.unwrap();
let mut buf = [Default::default(); anim::MAX_BONE_COUNT];
state.update(
renderer,
@ -315,7 +324,7 @@ impl Scene {
Rgba::broadcast(1.0),
scene_data.delta_time,
1.0,
&model[0],
&model,
0,
true,
false,
@ -354,8 +363,8 @@ impl Scene {
.0;
renderer.render_figure(
&model[0],
&self.col_lights.texture(),
&model.models[0],
&self.col_lights.texture(model),
&self.data,
self.figure_state.locals(),
self.figure_state.bone_consts(),
@ -365,8 +374,8 @@ impl Scene {
if let Some((model, state)) = &self.backdrop {
renderer.render_figure(
model,
&self.col_lights.texture(),
&model.models[0],
&self.col_lights.texture(model),
&self.data,
state.locals(),
state.bone_consts(),

View File

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

View File

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

View File

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

View File

@ -43,13 +43,12 @@ use conrod_core::{
widget::{self, id::Generator},
Rect, Scalar, UiBuilder, UiCell,
};
use core::{convert::TryInto, f32, f64, ops::Range};
use graphic::{Rotation, TexId};
use hashbrown::hash_map::Entry;
use std::{
f32, f64,
fs::File,
io::{BufReader, Read},
ops::Range,
sync::Arc,
time::Duration,
};
@ -67,7 +66,7 @@ enum DrawKind {
Plain,
}
enum DrawCommand {
Draw { kind: DrawKind, verts: Range<usize> },
Draw { kind: DrawKind, verts: Range<u32> },
Scissor(Aabr<u16>),
WorldPos(Option<usize>),
}
@ -75,14 +74,28 @@ impl DrawCommand {
fn image(verts: Range<usize>, id: TexId) -> DrawCommand {
DrawCommand::Draw {
kind: DrawKind::Image(id),
verts,
verts: verts
.start
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!")
..verts
.end
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!"),
}
}
fn plain(verts: Range<usize>) -> DrawCommand {
DrawCommand::Draw {
kind: DrawKind::Plain,
verts,
verts: verts
.start
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!")
..verts
.end
.try_into()
.expect("Vertex count for UI rendering does not fit in a u32!"),
}
}
}
@ -981,7 +994,7 @@ impl Ui {
DrawKind::Plain => self.cache.glyph_cache_tex(),
};
let model = self.model.submodel(verts.clone());
renderer.render_ui_element(&model, tex, scissor, globals, locals);
renderer.render_ui_element(model, tex, scissor, globals, locals);
},
}
}

View File

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

View File

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

View File

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

View File

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

View File

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