mirror of
https://gitlab.com/veloren/veloren.git
synced 2024-08-30 18:12:32 +00:00
Merge more models into one mesh than we did previously.
This commit is contained in:
parent
3155c31e66
commit
10245e0c1b
@ -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 {
|
||||
|
@ -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)
|
||||
|
@ -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 {
|
||||
|
@ -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) }
|
||||
}
|
||||
|
@ -4,7 +4,6 @@
|
||||
#![feature(
|
||||
arbitrary_enum_discriminant,
|
||||
const_checked_int_methods,
|
||||
const_if_match,
|
||||
option_unwrap_none,
|
||||
bool_to_option,
|
||||
label_break_value,
|
||||
|
@ -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
|
||||
|
@ -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))
|
||||
|
@ -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)]
|
||||
|
@ -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
|
||||
|
@ -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),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1 +1 @@
|
||||
nightly-2020-06-22
|
||||
nightly-2020-08-14
|
||||
|
@ -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) {
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
@ -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()),
|
||||
|
@ -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(),
|
||||
|
@ -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 { .. })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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| {
|
||||
|
@ -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
|
||||
|
@ -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]
|
||||
|
@ -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,30 +363,23 @@ where
|
||||
},
|
||||
);
|
||||
|
||||
let bounds = Aabb {
|
||||
min: Vec3::zero(),
|
||||
// NOTE: Safe because greedy_size fit in u16.
|
||||
max: greedy_size.map(|e| e as u16),
|
||||
};
|
||||
(
|
||||
bounds,
|
||||
Box::new(move |col_lights_info| {
|
||||
let mut data = data;
|
||||
draw_col_lights(
|
||||
col_lights_info,
|
||||
&mut data,
|
||||
todo_rects,
|
||||
draw_delta,
|
||||
get_light,
|
||||
get_color,
|
||||
get_opacity,
|
||||
TerrainVertex::make_col_light,
|
||||
);
|
||||
}),
|
||||
)
|
||||
Box::new(move |col_lights_info| {
|
||||
let mut data = data;
|
||||
draw_col_lights(
|
||||
col_lights_info,
|
||||
&mut data,
|
||||
todo_rects,
|
||||
draw_delta,
|
||||
get_light,
|
||||
get_color,
|
||||
get_opacity,
|
||||
TerrainVertex::make_col_light,
|
||||
);
|
||||
})
|
||||
}
|
||||
|
||||
// Greedy meshing a single cross-section.
|
||||
/// Greedy meshing a single cross-section.
|
||||
// TODO: See if we can speed a lot of this up using SIMD.
|
||||
fn greedy_mesh_cross_section<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>,
|
||||
|
@ -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;
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)?,
|
||||
})
|
||||
}
|
||||
|
@ -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> {
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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>));
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,
|
||||
|
@ -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 greedy = FigureModel::make_greedy();
|
||||
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);
|
||||
});
|
||||
|
||||
// Update the figure bounds to the largest granularity seen so far
|
||||
// (NOTE: this is more than a little imeprfect).
|
||||
//
|
||||
// FIXME: Maybe use the default bone position in the idle animation
|
||||
// to figure this out instead?
|
||||
figure_bounds.expand_to_contain(bounds);
|
||||
});
|
||||
col_lights
|
||||
.create_figure(renderer, greedy, (opaque, figure_bounds))
|
||||
.unwrap()
|
||||
};
|
||||
// NOTE: vertex_start and vertex_end *should* fit in a u32, by the
|
||||
// following logic:
|
||||
//
|
||||
// Our new figure maximum is constrained to at most 2^8 × 2^8 × 2^8.
|
||||
// This uses at most 24 bits to store every vertex exactly once.
|
||||
// Greedy meshing can store each vertex in up to 3 quads, we have 3
|
||||
// greedy models, and we store 1.5x the vertex count, so the maximum
|
||||
// total space a model can take up is 3 * 3 * 1.5 * 2^24; rounding
|
||||
// up to 4 * 4 * 2^24 gets us to 2^28, which clearly still fits in a
|
||||
// u32.
|
||||
//
|
||||
// (We could also, though we prefer not to, reason backwards from the
|
||||
// maximum figure texture size of 2^15 × 2^15, also fits in a u32; we
|
||||
// can also see that, since we can have at most one texture entry per
|
||||
// vertex, any texture atlas of size 2^14 × 2^14 or higher should be
|
||||
// able to store data for any figure. So the only reason we would fail
|
||||
// here would be if the user's computer could not store a texture large
|
||||
// enough to fit all the LOD models for the figure, not for fundamental
|
||||
// reasonS related to fitting in a u32).
|
||||
//
|
||||
// Therefore, these casts are safe.
|
||||
vertex_start as u32..opaque.vertices().len() as u32
|
||||
};
|
||||
|
||||
fn generate_mesh<'a>(
|
||||
greedy: &mut GreedyMesh<'a>,
|
||||
opaque_mesh: &mut Mesh<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),
|
||||
];
|
||||
(
|
||||
[
|
||||
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
|
||||
});
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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(),
|
||||
|
@ -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 =
|
||||
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;
|
||||
// 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,
|
||||
&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,
|
||||
|
@ -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.
|
||||
|
@ -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)) }
|
||||
|
@ -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);
|
||||
},
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,6 @@
|
||||
#![allow(clippy::option_map_unit_fn)]
|
||||
#![feature(
|
||||
arbitrary_enum_discriminant,
|
||||
const_if_match,
|
||||
const_generics,
|
||||
const_panic,
|
||||
label_break_value,
|
||||
|
@ -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 {
|
||||
|
@ -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;
|
||||
|
@ -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)]
|
||||
|
@ -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()
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user