Merge branch 'zesterer/better-char-select-scene' into 'master'

Render render LoD terrain on char select, remove old backdrop figure

See merge request veloren/veloren!4254
This commit is contained in:
Joshua Barretto 2024-01-16 18:51:04 +00:00
commit a852298010
17 changed files with 343 additions and 267 deletions

View File

@ -77,6 +77,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Linearize light colors on the CPU rather than in shaders on the GPU - Linearize light colors on the CPU rather than in shaders on the GPU
- You can no longer stack self buffs - You can no longer stack self buffs
- Renamed "Burning Potion" to "Potion of Combustion" - Renamed "Burning Potion" to "Potion of Combustion"
- Render LoD terrain on the character selection screen
### Removed ### Removed
- Medium and large potions from all loot tables - Medium and large potions from all loot tables

View File

@ -151,6 +151,10 @@ float ShadowCalculationPoint(uint lightIndex, vec3 fragToLight, vec3 fragNorm, /
float ShadowCalculationDirected(in vec3 fragPos)//in vec4 /*light_pos[2]*/sun_pos, vec3 fragPos) float ShadowCalculationDirected(in vec3 fragPos)//in vec4 /*light_pos[2]*/sun_pos, vec3 fragPos)
{ {
// Don't try to calculate directed shadows if there are no directed light sources
// Applies, for example, in the char select menu
if (light_shadow_count.z < 1) { return 1.0; }
float bias = 0.000;//0.0005;//-0.0001;// 0.05 / (2.0 * view_distance.x); float bias = 0.000;//0.0005;//-0.0001;// 0.05 / (2.0 * view_distance.x);
float diskRadius = 0.01; float diskRadius = 0.01;
const vec3 sampleOffsetDirections[20] = vec3[] const vec3 sampleOffsetDirections[20] = vec3[]

Binary file not shown.

View File

@ -245,6 +245,7 @@ pub struct Client {
available_recipes: HashMap<String, Option<SpriteKind>>, available_recipes: HashMap<String, Option<SpriteKind>>,
lod_zones: HashMap<Vec2<i32>, lod::Zone>, lod_zones: HashMap<Vec2<i32>, lod::Zone>,
lod_last_requested: Option<Instant>, lod_last_requested: Option<Instant>,
lod_pos_fallback: Option<Vec2<f32>>,
force_update_counter: u64, force_update_counter: u64,
max_group_size: u32, max_group_size: u32,
@ -760,6 +761,7 @@ impl Client {
lod_zones: HashMap::new(), lod_zones: HashMap::new(),
lod_last_requested: None, lod_last_requested: None,
lod_pos_fallback: None,
force_update_counter: 0, force_update_counter: 0,
@ -901,7 +903,7 @@ impl Client {
| ClientGeneral::DeleteCharacter(_) | ClientGeneral::DeleteCharacter(_)
| ClientGeneral::Character(_, _) | ClientGeneral::Character(_, _)
| ClientGeneral::Spectate(_) => &mut self.character_screen_stream, | ClientGeneral::Spectate(_) => &mut self.character_screen_stream,
//Only in game // Only in game
ClientGeneral::ControllerInputs(_) ClientGeneral::ControllerInputs(_)
| ClientGeneral::ControlEvent(_) | ClientGeneral::ControlEvent(_)
| ClientGeneral::ControlAction(_) | ClientGeneral::ControlAction(_)
@ -922,7 +924,7 @@ impl Client {
} }
&mut self.in_game_stream &mut self.in_game_stream
}, },
//Only in game, terrain // Terrain
ClientGeneral::TerrainChunkRequest { .. } ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. } => { | ClientGeneral::LodZoneRequest { .. } => {
#[cfg(feature = "tracy")] #[cfg(feature = "tracy")]
@ -931,7 +933,7 @@ impl Client {
} }
&mut self.terrain_stream &mut self.terrain_stream
}, },
//Always possible // Always possible
ClientGeneral::ChatMsg(_) ClientGeneral::ChatMsg(_)
| ClientGeneral::Command(_, _) | ClientGeneral::Command(_, _)
| ClientGeneral::Terminate => &mut self.general_stream, | ClientGeneral::Terminate => &mut self.general_stream,
@ -1209,6 +1211,10 @@ impl Client {
pub fn lod_zones(&self) -> &HashMap<Vec2<i32>, lod::Zone> { &self.lod_zones } pub fn lod_zones(&self) -> &HashMap<Vec2<i32>, lod::Zone> { &self.lod_zones }
/// Set the fallback position used for loading LoD zones when the client
/// entity does not have a position.
pub fn set_lod_pos_fallback(&mut self, pos: Vec2<f32>) { self.lod_pos_fallback = Some(pos); }
/// Returns whether the specified recipe can be crafted and the sprite, if /// Returns whether the specified recipe can be crafted and the sprite, if
/// any, that is required to do so. /// any, that is required to do so.
pub fn can_craft_recipe(&self, recipe: &str, amount: u32) -> (bool, Option<SpriteKind>) { pub fn can_craft_recipe(&self, recipe: &str, amount: u32) -> (bool, Option<SpriteKind>) {
@ -2101,9 +2107,11 @@ impl Client {
let now = Instant::now(); let now = Instant::now();
self.pending_chunks self.pending_chunks
.retain(|_, created| now.duration_since(*created) < Duration::from_secs(3)); .retain(|_, created| now.duration_since(*created) < Duration::from_secs(3));
}
if let Some(lod_pos) = pos.map(|p| p.0.xy()).or(self.lod_pos_fallback) {
// Manage LoD zones // Manage LoD zones
let lod_zone = pos.0.xy().map(|e| lod::from_wpos(e as i32)); let lod_zone = lod_pos.map(|e| lod::from_wpos(e as i32));
// Request LoD zones that are in range // Request LoD zones that are in range
if self if self

View File

@ -129,7 +129,6 @@ impl ClientMsg {
| ClientGeneral::ExitInGame | ClientGeneral::ExitInGame
| ClientGeneral::PlayerPhysics { .. } | ClientGeneral::PlayerPhysics { .. }
| ClientGeneral::TerrainChunkRequest { .. } | ClientGeneral::TerrainChunkRequest { .. }
| ClientGeneral::LodZoneRequest { .. }
| ClientGeneral::UnlockSkill(_) | ClientGeneral::UnlockSkill(_)
| ClientGeneral::RequestSiteInfo(_) | ClientGeneral::RequestSiteInfo(_)
| ClientGeneral::RequestPlayerPhysics { .. } | ClientGeneral::RequestPlayerPhysics { .. }
@ -141,7 +140,9 @@ impl ClientMsg {
//Always possible //Always possible
ClientGeneral::ChatMsg(_) ClientGeneral::ChatMsg(_)
| ClientGeneral::Command(_, _) | ClientGeneral::Command(_, _)
| ClientGeneral::Terminate => true, | ClientGeneral::Terminate
// LodZoneRequest is required by the char select screen
| ClientGeneral::LodZoneRequest { .. } => true,
} }
}, },
ClientMsg::Ping(_) => true, ClientMsg::Ping(_) => true,

View File

@ -330,7 +330,6 @@ impl ServerMsg {
| ServerGeneral::InventoryUpdate(_, _) | ServerGeneral::InventoryUpdate(_, _)
| ServerGeneral::GroupInventoryUpdate(_, _, _) | ServerGeneral::GroupInventoryUpdate(_, _, _)
| ServerGeneral::TerrainChunkUpdate { .. } | ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::LodZoneUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_) | ServerGeneral::TerrainBlockUpdates(_)
| ServerGeneral::SetViewDistance(_) | ServerGeneral::SetViewDistance(_)
| ServerGeneral::Outcomes(_) | ServerGeneral::Outcomes(_)
@ -354,7 +353,8 @@ impl ServerMsg {
| ServerGeneral::CreateEntity(_) | ServerGeneral::CreateEntity(_)
| ServerGeneral::DeleteEntity(_) | ServerGeneral::DeleteEntity(_)
| ServerGeneral::Disconnect(_) | ServerGeneral::Disconnect(_)
| ServerGeneral::Notification(_) => true, | ServerGeneral::Notification(_)
| ServerGeneral::LodZoneUpdate { .. } => true,
} }
}, },
ServerMsg::Ping(_) => true, ServerMsg::Ping(_) => true,

View File

@ -195,7 +195,7 @@ impl Client {
| ServerGeneral::SpectatePosition(_) => { | ServerGeneral::SpectatePosition(_) => {
PreparedMsg::new(2, &g, &self.in_game_stream_params) PreparedMsg::new(2, &g, &self.in_game_stream_params)
}, },
//In-game related, terrain // Terrain
ServerGeneral::TerrainChunkUpdate { .. } ServerGeneral::TerrainChunkUpdate { .. }
| ServerGeneral::LodZoneUpdate { .. } | ServerGeneral::LodZoneUpdate { .. }
| ServerGeneral::TerrainBlockUpdates(_) => { | ServerGeneral::TerrainBlockUpdates(_) => {

View File

@ -61,61 +61,63 @@ impl<'a> System<'a> for Sys {
|(chunk_send_emitter, server_emitter), (entity, client, maybe_presence)| { |(chunk_send_emitter, server_emitter), (entity, client, maybe_presence)| {
let mut chunk_requests = Vec::new(); let mut chunk_requests = Vec::new();
let _ = super::try_recv_all(client, 5, |client, msg| { let _ = super::try_recv_all(client, 5, |client, msg| {
let presence = match maybe_presence { // SPECIAL CASE: LOD zone requests can be sent by non-present players
Some(g) => g, if let ClientGeneral::LodZoneRequest { key } = &msg {
None => { client.send(ServerGeneral::LodZoneUpdate {
debug!(?entity, "client is not in_game, ignoring msg"); key: *key,
trace!(?msg, "ignored msg content"); zone: lod.zone(*key).clone(),
if matches!(msg, ClientGeneral::TerrainChunkRequest { .. }) { })?;
network_metrics.chunks_request_dropped.inc(); } else {
} let presence = match maybe_presence {
return Ok(()); Some(g) => g,
}, None => {
}; debug!(?entity, "client is not in_game, ignoring msg");
match msg { trace!(?msg, "ignored msg content");
ClientGeneral::TerrainChunkRequest { key } => { if matches!(msg, ClientGeneral::TerrainChunkRequest { .. }) {
let in_vd = if let Some(pos) = positions.get(entity) { network_metrics.chunks_request_dropped.inc();
pos.0.xy().map(|e| e as f64).distance_squared(
key.map(|e| e as f64 + 0.5)
* TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
) < ((presence.terrain_view_distance.current() as f64 - 1.0
+ 2.5 * 2.0_f64.sqrt())
* TerrainChunkSize::RECT_SIZE.x as f64)
.powi(2)
} else {
true
};
if in_vd {
if terrain.get_key_arc(key).is_some() {
network_metrics.chunks_served_from_memory.inc();
chunk_send_emitter.emit(ChunkSendEntry {
chunk_key: key,
entity,
});
} else {
network_metrics.chunks_generation_triggered.inc();
chunk_requests.push(ChunkRequest { entity, key });
} }
} else { return Ok(());
network_metrics.chunks_request_dropped.inc(); },
} };
}, match msg {
ClientGeneral::LodZoneRequest { key } => { ClientGeneral::TerrainChunkRequest { key } => {
client.send(ServerGeneral::LodZoneUpdate { let in_vd = if let Some(pos) = positions.get(entity) {
key, pos.0.xy().map(|e| e as f64).distance_squared(
zone: lod.zone(key).clone(), key.map(|e| e as f64 + 0.5)
})?; * TerrainChunkSize::RECT_SIZE.map(|e| e as f64),
}, ) < ((presence.terrain_view_distance.current() as f64 - 1.0
_ => { + 2.5 * 2.0_f64.sqrt())
debug!( * TerrainChunkSize::RECT_SIZE.x as f64)
"Kicking possibly misbehaving client due to invalud terrain \ .powi(2)
request" } else {
); true
server_emitter.emit(ServerEvent::ClientDisconnect( };
entity, if in_vd {
common::comp::DisconnectReason::NetworkError, if terrain.get_key_arc(key).is_some() {
)); network_metrics.chunks_served_from_memory.inc();
}, chunk_send_emitter.emit(ChunkSendEntry {
chunk_key: key,
entity,
});
} else {
network_metrics.chunks_generation_triggered.inc();
chunk_requests.push(ChunkRequest { entity, key });
}
} else {
network_metrics.chunks_request_dropped.inc();
}
},
_ => {
debug!(
"Kicking possibly misbehaving client due to invalud terrain \
request"
);
server_emitter.emit(ServerEvent::ClientDisconnect(
entity,
common::comp::DisconnectReason::NetworkError,
));
},
}
} }
Ok(()) Ok(())
}); });

View File

@ -16,7 +16,8 @@
let_chains, let_chains,
generic_const_exprs, generic_const_exprs,
maybe_uninit_uninit_array, maybe_uninit_uninit_array,
maybe_uninit_array_assume_init maybe_uninit_array_assume_init,
closure_lifetime_binder
)] )]
#![recursion_limit = "2048"] #![recursion_limit = "2048"]

View File

@ -26,10 +26,12 @@ pub struct CharSelectionState {
impl CharSelectionState { impl CharSelectionState {
/// Create a new `CharSelectionState`. /// Create a new `CharSelectionState`.
pub fn new(global_state: &mut GlobalState, client: Rc<RefCell<Client>>) -> Self { pub fn new(global_state: &mut GlobalState, client: Rc<RefCell<Client>>) -> Self {
let sprite_render_context = (global_state.lazy_init)(global_state.window.renderer_mut());
let scene = Scene::new( let scene = Scene::new(
global_state.window.renderer_mut(), global_state.window.renderer_mut(),
Some("fixture.selection_bg"), &mut client.borrow_mut(),
&client.borrow(), &global_state.settings,
sprite_render_context,
); );
let char_selection_ui = CharSelectionUi::new(global_state, &client.borrow()); let char_selection_ui = CharSelectionUi::new(global_state, &client.borrow());
@ -224,8 +226,12 @@ impl PlayState for CharSelectionState {
as f32, as f32,
}; };
self.scene self.scene.maintain(
.maintain(global_state.window.renderer_mut(), scene_data, loadout); global_state.window.renderer_mut(),
scene_data,
loadout,
&client,
);
} }
// Tick the client (currently only to keep the connection alive). // Tick the client (currently only to keep the connection alive).

View File

@ -1570,9 +1570,10 @@ pub struct AltIndices {
/// The mode with which culling based on the camera position relative to the /// The mode with which culling based on the camera position relative to the
/// terrain is performed. /// terrain is performed.
#[derive(Copy, Clone)] #[derive(Copy, Clone, Default)]
pub enum CullingMode { pub enum CullingMode {
/// We need to render all elements of the given structure /// We need to render all elements of the given structure
#[default]
None, None,
/// We only need to render surface and shallow (i.e: in the overlapping /// We only need to render surface and shallow (i.e: in the overlapping
/// region) elements of the structure /// region) elements of the structure

View File

@ -677,6 +677,9 @@ impl Camera {
/// Get the orientation of the camera. /// Get the orientation of the camera.
pub fn get_orientation(&self) -> Vec3<f32> { self.ori } pub fn get_orientation(&self) -> Vec3<f32> { self.ori }
/// Get the orientation that the camera is moving toward.
pub fn get_tgt_orientation(&self) -> Vec3<f32> { self.tgt_ori }
/// Get the field of view of the camera in radians, taking into account /// Get the field of view of the camera in radians, taking into account
/// fixation. /// fixation.
pub fn get_effective_fov(&self) -> f32 { self.fov * self.fixate } pub fn get_effective_fov(&self) -> f32 { self.fov * self.fixate }

View File

@ -13,8 +13,7 @@ use crate::{
}, },
scene::{ scene::{
camera::CameraMode, camera::CameraMode,
terrain::{get_sprite_instances, BlocksOfInterest, SPRITE_LOD_LEVELS}, terrain::{get_sprite_instances, BlocksOfInterest, SpriteRenderState, SPRITE_LOD_LEVELS},
Terrain,
}, },
}; };
use anim::Skeleton; use anim::Skeleton;
@ -632,7 +631,7 @@ where
extra: <Skel::Body as BodySpec>::Extra, extra: <Skel::Body as BodySpec>::Extra,
tick: u64, tick: u64,
slow_jobs: &SlowJobPool, slow_jobs: &SlowJobPool,
terrain: &Terrain, sprite_render_state: &SpriteRenderState,
) -> (TerrainModelEntryLod<'c>, &'c Skel::Attr) ) -> (TerrainModelEntryLod<'c>, &'c Skel::Attr)
where where
for<'a> &'a Skel::Body: Into<Skel::Attr>, for<'a> &'a Skel::Body: Into<Skel::Attr>,
@ -695,8 +694,8 @@ where
let key = v.key().clone(); let key = v.key().clone();
let slot = Arc::new(atomic::AtomicCell::new(None)); let slot = Arc::new(atomic::AtomicCell::new(None));
let manifests = self.manifests.clone(); let manifests = self.manifests.clone();
let sprite_data = Arc::clone(&terrain.sprite_data); let sprite_data = Arc::clone(&sprite_render_state.sprite_data);
let sprite_config = Arc::clone(&terrain.sprite_config); let sprite_config = Arc::clone(&sprite_render_state.sprite_config);
let slot_ = Arc::clone(&slot); let slot_ = Arc::clone(&slot);
slow_jobs.spawn("FIGURE_MESHING", move || { slow_jobs.spawn("FIGURE_MESHING", move || {

View File

@ -6288,7 +6288,7 @@ impl FigureMgr {
Arc::clone(vol), Arc::clone(vol),
tick, tick,
&slow_jobs, &slow_jobs,
terrain, &terrain.sprite_render_state,
); );
let state = self let state = self
@ -6315,7 +6315,7 @@ impl FigureMgr {
(), (),
tick, tick,
&slow_jobs, &slow_jobs,
terrain, &terrain.sprite_render_state,
) )
} else { } else {
// No way to determine model (this is okay, we might just not have received // No way to determine model (this is okay, we might just not have received
@ -7535,7 +7535,7 @@ impl FigureStateMeta {
pub struct FigureState<S, D = ()> { pub struct FigureState<S, D = ()> {
meta: FigureStateMeta, meta: FigureStateMeta,
skeleton: S, skeleton: S,
extra: D, pub extra: D,
} }
impl<S, D> Deref for FigureState<S, D> { impl<S, D> Deref for FigureState<S, D> {

View File

@ -1430,7 +1430,7 @@ impl Scene {
// Draws sprites // Draws sprites
let mut sprite_drawer = first_pass.draw_sprites( let mut sprite_drawer = first_pass.draw_sprites(
&self.terrain.sprite_globals, &self.terrain.sprite_globals,
&self.terrain.sprite_atlas_textures, &self.terrain.sprite_render_state.sprite_atlas_textures,
); );
self.figure_mgr.render_sprites( self.figure_mgr.render_sprites(
&mut sprite_drawer, &mut sprite_drawer,

View File

@ -1,35 +1,30 @@
use crate::{ use crate::{
mesh::{greedy::GreedyMesh, segment::generate_mesh_base_vol_figure},
render::{ render::{
create_skybox_mesh, pipelines::FigureSpriteAtlasData, BoneMeshes, Consts, FigureModel, create_skybox_mesh, pipelines::terrain::BoundLocals as BoundTerrainLocals, AltIndices,
FirstPassDrawer, GlobalModel, Globals, GlobalsBindGroup, Light, LodData, Mesh, Model, Consts, FirstPassDrawer, GlobalModel, Globals, GlobalsBindGroup, Light, Model,
PointLightMatrix, RainOcclusionLocals, Renderer, Shadow, ShadowLocals, SkyboxVertex, PointLightMatrix, RainOcclusionLocals, Renderer, Shadow, ShadowLocals, SkyboxVertex,
TerrainVertex, SpriteGlobalsBindGroup,
}, },
scene::{ scene::{
camera::{self, Camera, CameraMode}, camera::{self, Camera, CameraMode},
figure::{ figure::{FigureAtlas, FigureModelCache, FigureState, FigureUpdateCommonParameters},
load_mesh, FigureAtlas, FigureModelCache, FigureModelEntry, FigureState, terrain::{SpriteRenderContext, SpriteRenderState},
FigureUpdateCommonParameters, CloudsLocals, CullingMode, Lod, PostProcessLocals,
},
}, },
window::{Event, PressState}, window::{Event, PressState},
Settings,
}; };
use anim::{ use anim::{character::CharacterSkeleton, ship::ShipSkeleton, Animation};
character::{CharacterSkeleton, IdleAnimation, SkeletonAttr},
fixture::FixtureSkeleton,
Animation,
};
use client::Client; use client::Client;
use common::{ use common::{
comp::{ comp::{
humanoid, humanoid,
inventory::{slot::EquipSlot, Inventory}, inventory::{slot::EquipSlot, Inventory},
item::ItemKind, item::ItemKind,
ship,
}, },
figure::Segment,
slowjob::SlowJobPool, slowjob::SlowJobPool,
terrain::BlockKind, terrain::{BlockKind, CoordinateConversions},
vol::{BaseVol, ReadVol}, vol::{BaseVol, ReadVol},
}; };
use vek::*; use vek::*;
@ -46,18 +41,6 @@ impl ReadVol for VoidVol {
fn get(&self, _pos: Vec3<i32>) -> Result<&'_ Self::Vox, Self::Error> { Ok(&()) } fn get(&self, _pos: Vec3<i32>) -> Result<&'_ Self::Vox, Self::Error> { Ok(&()) }
} }
fn generate_mesh(
greedy: &mut GreedyMesh<'_, FigureSpriteAtlasData>,
mesh: &mut Mesh<TerrainVertex>,
segment: Segment,
offset: Vec3<f32>,
bone_idx: u8,
) -> BoneMeshes {
let (opaque, _, /* shadow */ _, bounds) =
generate_mesh_base_vol_figure(segment, (greedy, mesh, offset, Vec3::one(), bone_idx));
(opaque /* , shadow */, bounds)
}
struct Skybox { struct Skybox {
model: Model<SkyboxVertex>, model: Model<SkyboxVertex>,
} }
@ -68,17 +51,22 @@ pub struct Scene {
camera: Camera, camera: Camera,
skybox: Skybox, skybox: Skybox,
lod: LodData, lod: Lod,
map_bounds: Vec2<f32>, map_bounds: Vec2<f32>,
figure_atlas: FigureAtlas, figure_atlas: FigureAtlas,
backdrop: Option<(FigureModelEntry<1>, FigureState<FixtureSkeleton>)>, sprite_render_state: SpriteRenderState,
figure_model_cache: FigureModelCache, sprite_globals: SpriteGlobalsBindGroup,
figure_state: Option<FigureState<CharacterSkeleton>>,
//turning_camera: bool, turning_camera: bool,
turning_character: bool,
char_ori: f32, char_pos: Vec3<f32>,
char_state: Option<FigureState<CharacterSkeleton>>,
char_model_cache: FigureModelCache<CharacterSkeleton>,
airship_pos: Vec3<f32>,
airship_state: Option<FigureState<ShipSkeleton, BoundTerrainLocals>>,
airship_model_cache: FigureModelCache<ShipSkeleton>,
} }
pub struct SceneData<'a> { pub struct SceneData<'a> {
@ -95,8 +83,13 @@ pub struct SceneData<'a> {
} }
impl Scene { impl Scene {
pub fn new(renderer: &mut Renderer, backdrop: Option<&str>, client: &Client) -> Self { pub fn new(
let start_angle = 90.0f32.to_radians(); renderer: &mut Renderer,
client: &mut Client,
settings: &Settings,
sprite_render_context: SpriteRenderContext,
) -> Self {
let start_angle = -90.0f32.to_radians();
let resolution = renderer.resolution().map(|e| e as f32); let resolution = renderer.resolution().map(|e| e as f32);
let map_bounds = Vec2::new( let map_bounds = Vec2::new(
@ -105,11 +98,10 @@ impl Scene {
); );
let mut camera = Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson); let mut camera = Camera::new(resolution.x / resolution.y, CameraMode::ThirdPerson);
camera.set_focus_pos(Vec3::unit_z() * 1.5);
camera.set_distance(3.4); camera.set_distance(3.4);
camera.set_orientation(Vec3::new(start_angle, 0.0, 0.0)); camera.set_orientation(Vec3::new(start_angle, 0.1, 0.0));
let mut figure_atlas = FigureAtlas::new(renderer); let figure_atlas = FigureAtlas::new(renderer);
let data = GlobalModel { let data = GlobalModel {
globals: renderer.create_consts(&[Globals::default()]), globals: renderer.create_consts(&[Globals::default()]),
@ -120,81 +112,48 @@ impl Scene {
.create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]), .create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]),
point_light_matrices: Box::new([PointLightMatrix::default(); 126]), point_light_matrices: Box::new([PointLightMatrix::default(); 126]),
}; };
let lod = LodData::dummy(renderer); let lod = Lod::new(renderer, client, settings);
let globals_bind_group = renderer.bind_globals(&data, &lod); let globals_bind_group = renderer.bind_globals(&data, lod.get_data());
let world = client.world_data();
let char_chunk = world.chunk_size().map(|e| e as i32 / 2);
let char_pos = char_chunk.cpos_to_wpos().map(|e| e as f32).with_z(
world
.lod_alt
.get(char_chunk)
.map_or(0.0, |z| *z as f32 + 48.0),
);
client.set_lod_pos_fallback(char_pos.xy());
client.set_lod_distance(settings.graphics.lod_distance);
Self { Self {
data,
globals_bind_group, globals_bind_group,
skybox: Skybox { skybox: Skybox {
model: renderer.create_model(&create_skybox_mesh()).unwrap(), model: renderer.create_model(&create_skybox_mesh()).unwrap(),
}, },
lod,
map_bounds, map_bounds,
figure_model_cache: FigureModelCache::new(),
figure_state: None,
backdrop: backdrop.map(|specifier| {
let mut state = FigureState::new(renderer, FixtureSkeleton, ());
let mut greedy = FigureModel::make_greedy();
let mut opaque_mesh = Mesh::new();
let (segment, offset) = load_mesh(specifier, Vec3::new(-55.0, -49.5, -2.0));
let (_opaque_mesh, bounds) =
generate_mesh(&mut greedy, &mut opaque_mesh, segment, offset, 0);
// 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 = 0..opaque_mesh.vertices().len() as u32;
let (atlas_texture_data, atlas_size) = greedy.finalize();
let model = figure_atlas.create_figure(
renderer,
atlas_texture_data,
atlas_size,
(opaque_mesh, bounds),
[range],
);
let mut buf = [Default::default(); anim::MAX_BONE_COUNT];
let common_params = FigureUpdateCommonParameters {
entity: None,
pos: anim::vek::Vec3::zero(),
ori: anim::vek::Quaternion::rotation_from_to_3d(
anim::vek::Vec3::unit_y(),
anim::vek::Vec3::new(start_angle.sin(), -start_angle.cos(), 0.0),
),
scale: 1.0,
mount_transform_pos: None,
body: None,
tools: (None, None),
col: Rgba::broadcast(1.0),
dt: 15.0, // Want to get there immediately.
_lpindex: 0,
_visible: true,
is_player: false,
_camera: &camera,
terrain: None,
ground_vel: Vec3::zero(),
};
state.update(
renderer,
None,
&mut buf,
&common_params,
1.0,
Some(&model),
(),
);
(model, state)
}),
figure_atlas, figure_atlas,
sprite_render_state: sprite_render_context.state,
sprite_globals: renderer.bind_sprite_globals(
&data,
lod.get_data(),
&sprite_render_context.sprite_verts_buffer,
),
lod,
data,
camera, camera,
//turning_camera: false, turning_camera: false,
turning_character: false, char_pos,
char_ori: -start_angle, char_state: None,
char_model_cache: FigureModelCache::new(),
airship_pos: char_pos - Vec3::unit_z() * 10.0,
airship_state: None,
airship_model_cache: FigureModelCache::new(),
} }
} }
@ -215,20 +174,15 @@ impl Scene {
}, },
Event::MouseButton(button, state) => { Event::MouseButton(button, state) => {
if state == PressState::Pressed { if state == PressState::Pressed {
//self.turning_camera = button == MouseButton::Right; self.turning_camera = button == MouseButton::Left;
self.turning_character = button == MouseButton::Left;
} else { } else {
//self.turning_camera = false; self.turning_camera = false;
self.turning_character = false;
} }
true true
}, },
Event::CursorMove(delta) => { Event::CursorMove(delta) => {
/*if self.turning_camera { if self.turning_camera {
self.camera.rotate_by(Vec3::new(delta.x * 0.01, 0.0, 0.0)) self.camera.rotate_by(delta.with_z(0.0) * 0.01);
}*/
if self.turning_character {
self.char_ori += delta.x * 0.01;
} }
true true
}, },
@ -242,7 +196,13 @@ impl Scene {
renderer: &mut Renderer, renderer: &mut Renderer,
scene_data: SceneData, scene_data: SceneData,
inventory: Option<&Inventory>, inventory: Option<&Inventory>,
client: &Client,
) { ) {
self.camera
.force_focus_pos(self.char_pos + Vec3::unit_z() * 1.5);
let ori = self.camera.get_tgt_orientation();
self.camera
.set_orientation(Vec3::new(ori.x, ori.y.max(-0.25), ori.z));
self.camera.update( self.camera.update(
scene_data.time, scene_data.time,
/* 1.0 / 60.0 */ scene_data.delta_time, /* 1.0 / 60.0 */ scene_data.delta_time,
@ -254,13 +214,18 @@ impl Scene {
view_mat, view_mat,
proj_mat, proj_mat,
cam_pos, cam_pos,
proj_mat_inv,
view_mat_inv,
.. ..
} = self.camera.dependents(); } = self.camera.dependents();
const VD: f32 = 115.0; // View Distance const VD: f32 = 0.0; // View Distance
const TIME: f64 = 8.6 * 60.0 * 60.0; const TIME: f64 = 8.6 * 60.0 * 60.0;
const SHADOW_NEAR: f32 = 1.0; const SHADOW_NEAR: f32 = 0.25;
const SHADOW_FAR: f32 = 25.0; const SHADOW_FAR: f32 = 1.0;
self.lod
.maintain(renderer, client, self.camera.get_focus_pos(), &self.camera);
renderer.update_consts(&mut self.data.globals, &[Globals::new( renderer.update_consts(&mut self.data.globals, &[Globals::new(
view_mat, view_mat,
@ -268,7 +233,7 @@ impl Scene {
cam_pos, cam_pos,
self.camera.get_focus_pos(), self.camera.get_focus_pos(),
VD, VD,
self.lod.tgt_detail as f32, self.lod.get_data().tgt_detail as f32,
self.map_bounds, self.map_bounds,
TIME, TIME,
scene_data.time, scene_data.time,
@ -288,8 +253,12 @@ impl Scene {
self.camera.get_mode(), self.camera.get_mode(),
250.0, 250.0,
)]); )]);
renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv));
renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
self.figure_model_cache self.char_model_cache
.clean(&mut self.figure_atlas, scene_data.tick);
self.airship_model_cache
.clean(&mut self.figure_atlas, scene_data.tick); .clean(&mut self.figure_atlas, scene_data.tick);
let item_info = |equip_slot| { let item_info = |equip_slot| {
@ -310,12 +279,37 @@ impl Scene {
let hands = (active_tool_hand, second_tool_hand); let hands = (active_tool_hand, second_tool_hand);
fn figure_params(
camera: &Camera,
dt: f32,
pos: Vec3<f32>,
) -> FigureUpdateCommonParameters<'_> {
FigureUpdateCommonParameters {
entity: None,
pos: pos.into(),
ori: anim::vek::Quaternion::identity().rotated_z(std::f32::consts::PI * -0.5),
scale: 1.0,
mount_transform_pos: None,
body: None,
tools: (None, None),
col: Rgba::broadcast(1.0),
dt,
_lpindex: 0,
_visible: true,
is_player: false,
_camera: camera,
terrain: None,
ground_vel: Vec3::zero(),
}
}
if let Some(body) = scene_data.body { if let Some(body) = scene_data.body {
let figure_state = self.figure_state.get_or_insert_with(|| { let char_state = self.char_state.get_or_insert_with(|| {
FigureState::new(renderer, CharacterSkeleton::default(), body) FigureState::new(renderer, CharacterSkeleton::default(), body)
}); });
let tgt_skeleton = IdleAnimation::update_skeleton( let params = figure_params(&self.camera, scene_data.delta_time, self.char_pos);
figure_state.skeleton_mut(), let tgt_skeleton = anim::character::IdleAnimation::update_skeleton(
char_state.skeleton_mut(),
( (
active_tool_kind, active_tool_kind,
second_tool_kind, second_tool_kind,
@ -324,51 +318,74 @@ impl Scene {
), ),
scene_data.time as f32, scene_data.time as f32,
&mut 0.0, &mut 0.0,
&SkeletonAttr::from(&body), &anim::character::SkeletonAttr::from(&body),
); );
let dt_lerp = (scene_data.delta_time * 15.0).min(1.0); let dt_lerp = (scene_data.delta_time * 15.0).min(1.0);
*figure_state.skeleton_mut() = *char_state.skeleton_mut() =
Lerp::lerp(&*figure_state.skeleton_mut(), &tgt_skeleton, dt_lerp); Lerp::lerp(&*char_state.skeleton_mut(), &tgt_skeleton, dt_lerp);
let (model, _) = self.char_model_cache.get_or_create_model(
let model = self renderer,
.figure_model_cache &mut self.figure_atlas,
.get_or_create_model( body,
renderer, inventory,
&mut self.figure_atlas, (),
body, scene_data.tick,
inventory, CameraMode::default(),
(), None,
scene_data.tick, scene_data.slow_job_pool,
CameraMode::default(), None,
None, );
scene_data.slow_job_pool, char_state.update(
None, renderer,
) None,
.0; &mut [Default::default(); anim::MAX_BONE_COUNT],
let mut buf = [Default::default(); anim::MAX_BONE_COUNT]; &params,
let common_params = FigureUpdateCommonParameters { 1.0,
entity: None, model,
pos: anim::vek::Vec3::zero(), body,
ori: anim::vek::Quaternion::rotation_from_to_3d( );
anim::vek::Vec3::unit_y(),
anim::vek::Vec3::new(self.char_ori.sin(), -self.char_ori.cos(), 0.0),
),
scale: 1.0,
mount_transform_pos: None,
body: None,
tools: (None, None),
col: Rgba::broadcast(1.0),
dt: scene_data.delta_time,
_lpindex: 0,
_visible: true,
is_player: false,
_camera: &self.camera,
terrain: None,
ground_vel: Vec3::zero(),
};
figure_state.update(renderer, None, &mut buf, &common_params, 1.0, model, body);
} }
let airship_body = ship::Body::DefaultAirship;
let airship_state = self.airship_state.get_or_insert_with(|| {
FigureState::new(renderer, ShipSkeleton::default(), airship_body)
});
let params = figure_params(&self.camera, scene_data.delta_time, self.airship_pos);
let tgt_skeleton = anim::ship::IdleAnimation::update_skeleton(
airship_state.skeleton_mut(),
(
None,
None,
scene_data.time as f32,
scene_data.time as f32,
(params.ori * Vec3::unit_y()).into(),
(params.ori * Vec3::unit_y()).into(),
),
scene_data.time as f32,
&mut 0.0,
&anim::ship::SkeletonAttr::from(&airship_body),
);
let dt_lerp = (scene_data.delta_time * 15.0).min(1.0);
*airship_state.skeleton_mut() =
Lerp::lerp(&*airship_state.skeleton_mut(), &tgt_skeleton, dt_lerp);
let (model, _) = self.airship_model_cache.get_or_create_terrain_model(
renderer,
&mut self.figure_atlas,
airship_body,
(),
scene_data.tick,
scene_data.slow_job_pool,
&self.sprite_render_state,
);
airship_state.update(
renderer,
None,
&mut [Default::default(); anim::MAX_BONE_COUNT],
&params,
1.0,
model,
airship_body,
);
} }
pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group } pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
@ -382,7 +399,7 @@ impl Scene {
) { ) {
let mut figure_drawer = drawer.draw_figures(); let mut figure_drawer = drawer.draw_figures();
if let Some(body) = body { if let Some(body) = body {
let model = &self.figure_model_cache.get_model( let model = &self.char_model_cache.get_model(
&self.figure_atlas, &self.figure_atlas,
body, body,
inventory, inventory,
@ -392,28 +409,61 @@ impl Scene {
None, None,
); );
if let Some((model, figure_state)) = model.zip(self.figure_state.as_ref()) { if let Some((model, char_state)) = model.zip(self.char_state.as_ref()) {
if let Some(lod) = model.lod_model(0) { if let Some(lod) = model.lod_model(0) {
figure_drawer.draw( figure_drawer.draw(
lod, lod,
figure_state.bound(), char_state.bound(),
self.figure_atlas.texture(ModelEntryRef::Figure(model)), self.figure_atlas.texture(ModelEntryRef::Figure(model)),
); );
} }
} }
} }
if let Some((model, state)) = &self.backdrop { let model = &self.airship_model_cache.get_model(
&self.figure_atlas,
ship::Body::DefaultAirship,
Default::default(),
tick,
CameraMode::default(),
None,
None,
);
if let Some((model, airship_state)) = model.zip(self.airship_state.as_ref()) {
if let Some(lod) = model.lod_model(0) { if let Some(lod) = model.lod_model(0) {
figure_drawer.draw( figure_drawer.draw(
lod, lod,
state.bound(), airship_state.bound(),
self.figure_atlas.texture(ModelEntryRef::Figure(model)), self.figure_atlas.texture(ModelEntryRef::Terrain(model)),
); );
} }
} }
drop(figure_drawer); drop(figure_drawer);
let mut sprite_drawer = drawer.draw_sprites(
&self.sprite_globals,
&self.sprite_render_state.sprite_atlas_textures,
);
if let (Some(sprite_instances), Some(data)) = (
self.airship_model_cache
.get_sprites(ship::Body::DefaultAirship),
self.airship_state.as_ref().map(|s| &s.extra),
) {
sprite_drawer.draw(
data,
&sprite_instances[0],
&AltIndices {
deep_end: 0,
underground_end: 0,
},
CullingMode::None,
);
}
drop(sprite_drawer);
self.lod.render(drawer, Default::default());
drawer.draw_skybox(&self.skybox.model); drawer.draw_skybox(&self.skybox.model);
} }
} }

View File

@ -483,10 +483,6 @@ pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
/// might significantly reduce the number of textures we need for /// might significantly reduce the number of textures we need for
/// particularly difficult locations. /// particularly difficult locations.
atlas: AtlasAllocator, atlas: AtlasAllocator,
/// FIXME: This could possibly become an `AssetHandle<SpriteSpec>`, to get
/// hot-reloading for free, but I am not sure if sudden changes of this
/// value would break something
pub sprite_config: Arc<SpriteSpec>,
chunks: HashMap<Vec2<i32>, TerrainChunkData>, chunks: HashMap<Vec2<i32>, TerrainChunkData>,
/// Temporary storage for dead chunks that might still be shadowing chunks /// Temporary storage for dead chunks that might still be shadowing chunks
/// in view. We wait until either the chunk definitely cannot be /// in view. We wait until either the chunk definitely cannot be
@ -516,9 +512,8 @@ pub struct Terrain<V: RectRasterableVol = TerrainChunk> {
// GPU data // GPU data
// Maps sprite kind + variant to data detailing how to render it // Maps sprite kind + variant to data detailing how to render it
pub sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>, pub sprite_render_state: SpriteRenderState,
pub sprite_globals: SpriteGlobalsBindGroup, pub sprite_globals: SpriteGlobalsBindGroup,
pub sprite_atlas_textures: Arc<AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>>,
/// As stated previously, this is always the very latest texture into which /// As stated previously, this is always the very latest texture into which
/// we allocate. Code cannot assume that this is the assigned texture /// we allocate. Code cannot assume that this is the assigned texture
/// for any particular chunk; look at the `texture` field in /// for any particular chunk; look at the `texture` field in
@ -533,12 +528,20 @@ impl TerrainChunkData {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct SpriteRenderContext { pub struct SpriteRenderState {
sprite_config: Arc<SpriteSpec>, /// FIXME: This could possibly become an `AssetHandle<SpriteSpec>`, to get
/// hot-reloading for free, but I am not sure if sudden changes of this
/// value would break something
pub sprite_config: Arc<SpriteSpec>,
// Maps sprite kind + variant to data detailing how to render it // Maps sprite kind + variant to data detailing how to render it
sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>, pub sprite_data: Arc<HashMap<(SpriteKind, usize), [SpriteData; SPRITE_LOD_LEVELS]>>,
sprite_atlas_textures: Arc<AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>>, pub sprite_atlas_textures: Arc<AtlasTextures<pipelines::sprite::Locals, FigureSpriteAtlasData>>,
sprite_verts_buffer: Arc<SpriteVerts>, }
#[derive(Clone)]
pub struct SpriteRenderContext {
pub state: SpriteRenderState,
pub sprite_verts_buffer: Arc<SpriteVerts>,
} }
pub type SpriteRenderContextLazy = Box<dyn FnMut(&mut Renderer) -> SpriteRenderContext>; pub type SpriteRenderContextLazy = Box<dyn FnMut(&mut Renderer) -> SpriteRenderContext>;
@ -705,10 +708,12 @@ impl SpriteRenderContext {
let sprite_verts_buffer = renderer.create_sprite_verts(sprite_mesh); let sprite_verts_buffer = renderer.create_sprite_verts(sprite_mesh);
Self { Self {
// TODO: these are all Arcs, would it makes sense to factor out the Arc? state: SpriteRenderState {
sprite_config: Arc::clone(&sprite_config), // TODO: these are all Arcs, would it makes sense to factor out the Arc?
sprite_data: Arc::new(sprite_data), sprite_config: Arc::clone(&sprite_config),
sprite_atlas_textures: Arc::new(sprite_atlas_textures), sprite_data: Arc::new(sprite_data),
sprite_atlas_textures: Arc::new(sprite_atlas_textures),
},
sprite_verts_buffer: Arc::new(sprite_verts_buffer), sprite_verts_buffer: Arc::new(sprite_verts_buffer),
} }
}; };
@ -732,7 +737,6 @@ impl<V: RectRasterableVol> Terrain<V> {
Self { Self {
atlas, atlas,
sprite_config: sprite_render_context.sprite_config,
chunks: HashMap::default(), chunks: HashMap::default(),
shadow_chunks: Vec::default(), shadow_chunks: Vec::default(),
mesh_send_tmp: send, mesh_send_tmp: send,
@ -740,8 +744,7 @@ impl<V: RectRasterableVol> Terrain<V> {
mesh_todo: HashMap::default(), mesh_todo: HashMap::default(),
mesh_todos_active: Arc::new(AtomicU64::new(0)), mesh_todos_active: Arc::new(AtomicU64::new(0)),
mesh_recv_overflow: 0.0, mesh_recv_overflow: 0.0,
sprite_data: sprite_render_context.sprite_data, sprite_render_state: sprite_render_context.state,
sprite_atlas_textures: sprite_render_context.sprite_atlas_textures,
sprite_globals: renderer.bind_sprite_globals( sprite_globals: renderer.bind_sprite_globals(
global_model, global_model,
lod_data, lod_data,
@ -1227,8 +1230,8 @@ impl<V: RectRasterableVol> Terrain<V> {
// Queue the worker thread. // Queue the worker thread.
let started_tick = todo.started_tick; let started_tick = todo.started_tick;
let sprite_data = Arc::clone(&self.sprite_data); let sprite_data = Arc::clone(&self.sprite_render_state.sprite_data);
let sprite_config = Arc::clone(&self.sprite_config); let sprite_config = Arc::clone(&self.sprite_render_state.sprite_config);
let cnt = Arc::clone(&self.mesh_todos_active); let cnt = Arc::clone(&self.mesh_todos_active);
cnt.fetch_add(1, Ordering::Relaxed); cnt.fetch_add(1, Ordering::Relaxed);
scene_data scene_data