mod cache; pub mod load; mod volume; pub use cache::FigureModelCache; pub use load::load_mesh; // TODO: Don't make this public. pub use volume::VolumeKey; use crate::{ ecs::comp::Interpolated, render::{ pipelines::{self, trail, ColLights}, ColLightInfo, FigureBoneData, FigureDrawer, FigureLocals, FigureModel, FigureShadowDrawer, Mesh, Quad, RenderError, Renderer, SubModel, TerrainVertex, }, scene::{ camera::{Camera, CameraMode, Dependents}, math, terrain::Terrain, SceneData, TrailMgr, RAIN_THRESHOLD, }, }; use anim::{ arthropod::ArthropodSkeleton, biped_large::BipedLargeSkeleton, biped_small::BipedSmallSkeleton, bird_large::BirdLargeSkeleton, bird_medium::BirdMediumSkeleton, character::CharacterSkeleton, dragon::DragonSkeleton, fish_medium::FishMediumSkeleton, fish_small::FishSmallSkeleton, golem::GolemSkeleton, item_drop::ItemDropSkeleton, object::ObjectSkeleton, quadruped_low::QuadrupedLowSkeleton, quadruped_medium::QuadrupedMediumSkeleton, quadruped_small::QuadrupedSmallSkeleton, ship::ShipSkeleton, theropod::TheropodSkeleton, Animation, Skeleton, }; use common::{ comp::{ inventory::slot::EquipSlot, item::{Hands, ItemKind, ToolKind}, Body, CharacterState, Collider, Controller, Health, Inventory, Item, ItemKey, Last, LightAnimation, LightEmitter, Ori, PhysicsState, PoiseState, Pos, Scale, Vel, }, link::Is, mounting::Rider, resources::{DeltaTime, Time}, states::{equipping, idle, utils::StageSection, wielding}, terrain::{Block, TerrainChunk, TerrainGrid}, uid::UidAllocator, vol::{ReadVol, RectRasterableVol}, }; use common_base::span; use common_state::State; use core::{ borrow::Borrow, convert::TryFrom, hash::Hash, ops::{Deref, DerefMut, Range}, }; use guillotiere::AtlasAllocator; use hashbrown::HashMap; use specs::{saveload::MarkerAllocator, Entity as EcsEntity, Join, LazyUpdate, WorldExt}; use std::sync::Arc; use treeculler::{BVol, BoundingSphere}; use vek::*; const DAMAGE_FADE_COEFFICIENT: f64 = 15.0; const MOVING_THRESHOLD: f32 = 0.2; const MOVING_THRESHOLD_SQR: f32 = MOVING_THRESHOLD * MOVING_THRESHOLD; /// camera data, figure LOD render distance. pub type CameraData<'a> = (&'a Camera, f32); /// Enough data to render a figure model. pub type FigureModelRef<'a> = ( &'a pipelines::figure::BoundLocals, SubModel<'a, TerrainVertex>, &'a ColLights, ); /// An entry holding enough information to draw or destroy a figure in a /// particular cache. pub struct FigureModelEntry { /// The estimated bounds of this figure, in voxels. This may not be very /// useful yet. _bounds: math::Aabb, /// Hypothetical texture atlas allocation data for the current figure. /// Will be useful if we decide to use a packed texture atlas for figures /// like we do for terrain. allocation: guillotiere::Allocation, /// Texture used to store color/light information for this figure entry. /* TODO: Consider using mipmaps instead of storing multiple texture atlases for different * LOD levels. */ col_lights: ColLights, /// Vertex ranges stored in this figure entry; there may be several for one /// figure, because of LOD models. lod_vertex_ranges: [Range; N], model: FigureModel, } impl FigureModelEntry { pub fn lod_model(&self, lod: usize) -> Option> { // Note: Range doesn't impl Copy even for trivially Cloneable things self.model .opaque .as_ref() .map(|m| m.submodel(self.lod_vertex_ranges[lod].clone())) } } struct FigureMgrStates { character_states: HashMap>, quadruped_small_states: HashMap>, quadruped_medium_states: HashMap>, quadruped_low_states: HashMap>, bird_medium_states: HashMap>, fish_medium_states: HashMap>, theropod_states: HashMap>, dragon_states: HashMap>, bird_large_states: HashMap>, fish_small_states: HashMap>, biped_large_states: HashMap>, biped_small_states: HashMap>, golem_states: HashMap>, object_states: HashMap>, item_drop_states: HashMap>, ship_states: HashMap>, volume_states: HashMap>, arthropod_states: HashMap>, } impl FigureMgrStates { pub fn default() -> Self { Self { character_states: HashMap::new(), quadruped_small_states: HashMap::new(), quadruped_medium_states: HashMap::new(), quadruped_low_states: HashMap::new(), bird_medium_states: HashMap::new(), fish_medium_states: HashMap::new(), theropod_states: HashMap::new(), dragon_states: HashMap::new(), bird_large_states: HashMap::new(), fish_small_states: HashMap::new(), biped_large_states: HashMap::new(), biped_small_states: HashMap::new(), golem_states: HashMap::new(), object_states: HashMap::new(), item_drop_states: HashMap::new(), ship_states: HashMap::new(), volume_states: HashMap::new(), arthropod_states: HashMap::new(), } } fn get_mut<'a, Q: ?Sized>( &'a mut self, body: &Body, entity: &Q, ) -> Option<&'a mut FigureStateMeta> where EcsEntity: Borrow, Q: Hash + Eq, { match body { Body::Humanoid(_) => self .character_states .get_mut(entity) .map(DerefMut::deref_mut), Body::QuadrupedSmall(_) => self .quadruped_small_states .get_mut(entity) .map(DerefMut::deref_mut), Body::QuadrupedMedium(_) => self .quadruped_medium_states .get_mut(entity) .map(DerefMut::deref_mut), Body::QuadrupedLow(_) => self .quadruped_low_states .get_mut(entity) .map(DerefMut::deref_mut), Body::BirdMedium(_) => self .bird_medium_states .get_mut(entity) .map(DerefMut::deref_mut), Body::FishMedium(_) => self .fish_medium_states .get_mut(entity) .map(DerefMut::deref_mut), Body::Theropod(_) => self .theropod_states .get_mut(entity) .map(DerefMut::deref_mut), Body::Dragon(_) => self.dragon_states.get_mut(entity).map(DerefMut::deref_mut), Body::BirdLarge(_) => self .bird_large_states .get_mut(entity) .map(DerefMut::deref_mut), Body::FishSmall(_) => self .fish_small_states .get_mut(entity) .map(DerefMut::deref_mut), Body::BipedLarge(_) => self .biped_large_states .get_mut(entity) .map(DerefMut::deref_mut), Body::BipedSmall(_) => self .biped_small_states .get_mut(entity) .map(DerefMut::deref_mut), Body::Golem(_) => self.golem_states.get_mut(entity).map(DerefMut::deref_mut), Body::Object(_) => self.object_states.get_mut(entity).map(DerefMut::deref_mut), Body::ItemDrop(_) => self .item_drop_states .get_mut(entity) .map(DerefMut::deref_mut), Body::Ship(ship) => { if ship.manifest_entry().is_some() { self.ship_states.get_mut(entity).map(DerefMut::deref_mut) } else { self.volume_states.get_mut(entity).map(DerefMut::deref_mut) } }, Body::Arthropod(_) => self .arthropod_states .get_mut(entity) .map(DerefMut::deref_mut), } } fn remove<'a, Q: ?Sized>(&'a mut self, body: &Body, entity: &Q) -> Option where EcsEntity: Borrow, Q: Hash + Eq, { match body { Body::Humanoid(_) => self.character_states.remove(entity).map(|e| e.meta), Body::QuadrupedSmall(_) => self.quadruped_small_states.remove(entity).map(|e| e.meta), Body::QuadrupedMedium(_) => self.quadruped_medium_states.remove(entity).map(|e| e.meta), Body::QuadrupedLow(_) => self.quadruped_low_states.remove(entity).map(|e| e.meta), Body::BirdMedium(_) => self.bird_medium_states.remove(entity).map(|e| e.meta), Body::FishMedium(_) => self.fish_medium_states.remove(entity).map(|e| e.meta), Body::Theropod(_) => self.theropod_states.remove(entity).map(|e| e.meta), Body::Dragon(_) => self.dragon_states.remove(entity).map(|e| e.meta), Body::BirdLarge(_) => self.bird_large_states.remove(entity).map(|e| e.meta), Body::FishSmall(_) => self.fish_small_states.remove(entity).map(|e| e.meta), Body::BipedLarge(_) => self.biped_large_states.remove(entity).map(|e| e.meta), Body::BipedSmall(_) => self.biped_small_states.remove(entity).map(|e| e.meta), Body::Golem(_) => self.golem_states.remove(entity).map(|e| e.meta), Body::Object(_) => self.object_states.remove(entity).map(|e| e.meta), Body::ItemDrop(_) => self.item_drop_states.remove(entity).map(|e| e.meta), Body::Ship(ship) => { if ship.manifest_entry().is_some() { self.ship_states.remove(entity).map(|e| e.meta) } else { self.volume_states.remove(entity).map(|e| e.meta) } }, Body::Arthropod(_) => self.arthropod_states.remove(entity).map(|e| e.meta), } } fn retain(&mut self, mut f: impl FnMut(&EcsEntity, &mut FigureStateMeta) -> bool) { span!(_guard, "retain", "FigureManagerStates::retain"); self.character_states.retain(|k, v| f(k, &mut *v)); self.quadruped_small_states.retain(|k, v| f(k, &mut *v)); self.quadruped_medium_states.retain(|k, v| f(k, &mut *v)); self.quadruped_low_states.retain(|k, v| f(k, &mut *v)); self.bird_medium_states.retain(|k, v| f(k, &mut *v)); self.fish_medium_states.retain(|k, v| f(k, &mut *v)); self.theropod_states.retain(|k, v| f(k, &mut *v)); self.dragon_states.retain(|k, v| f(k, &mut *v)); self.bird_large_states.retain(|k, v| f(k, &mut *v)); self.fish_small_states.retain(|k, v| f(k, &mut *v)); self.biped_large_states.retain(|k, v| f(k, &mut *v)); self.biped_small_states.retain(|k, v| f(k, &mut *v)); self.golem_states.retain(|k, v| f(k, &mut *v)); self.object_states.retain(|k, v| f(k, &mut *v)); self.item_drop_states.retain(|k, v| f(k, &mut *v)); self.ship_states.retain(|k, v| f(k, &mut *v)); self.volume_states.retain(|k, v| f(k, &mut *v)); self.arthropod_states.retain(|k, v| f(k, &mut *v)); } fn count(&self) -> usize { self.character_states.len() + self.quadruped_small_states.len() + self.character_states.len() + self.quadruped_medium_states.len() + self.quadruped_low_states.len() + self.bird_medium_states.len() + self.fish_medium_states.len() + self.theropod_states.len() + self.dragon_states.len() + self.bird_large_states.len() + self.fish_small_states.len() + self.biped_large_states.len() + self.biped_small_states.len() + self.golem_states.len() + self.object_states.len() + self.item_drop_states.len() + self.ship_states.len() + self.volume_states.len() + self.arthropod_states.len() } fn count_visible(&self) -> usize { self.character_states .iter() .filter(|(_, c)| c.visible()) .count() + self .quadruped_small_states .iter() .filter(|(_, c)| c.visible()) .count() + self .quadruped_medium_states .iter() .filter(|(_, c)| c.visible()) .count() + self .quadruped_low_states .iter() .filter(|(_, c)| c.visible()) .count() + self .bird_medium_states .iter() .filter(|(_, c)| c.visible()) .count() + self .theropod_states .iter() .filter(|(_, c)| c.visible()) .count() + self .dragon_states .iter() .filter(|(_, c)| c.visible()) .count() + self .fish_medium_states .iter() .filter(|(_, c)| c.visible()) .count() + self .bird_large_states .iter() .filter(|(_, c)| c.visible()) .count() + self .fish_small_states .iter() .filter(|(_, c)| c.visible()) .count() + self .biped_large_states .iter() .filter(|(_, c)| c.visible()) .count() + self .biped_small_states .iter() .filter(|(_, c)| c.visible()) .count() + self .golem_states .iter() .filter(|(_, c)| c.visible()) .count() + self .object_states .iter() .filter(|(_, c)| c.visible()) .count() + self .item_drop_states .iter() .filter(|(_, c)| c.visible()) .count() + self .arthropod_states .iter() .filter(|(_, c)| c.visible()) .count() + self.ship_states.iter().filter(|(_, c)| c.visible()).count() + self .volume_states .iter() .filter(|(_, c)| c.visible()) .count() } } pub struct FigureMgr { col_lights: FigureColLights, model_cache: FigureModelCache, theropod_model_cache: FigureModelCache, quadruped_small_model_cache: FigureModelCache, quadruped_medium_model_cache: FigureModelCache, quadruped_low_model_cache: FigureModelCache, bird_medium_model_cache: FigureModelCache, bird_large_model_cache: FigureModelCache, dragon_model_cache: FigureModelCache, fish_medium_model_cache: FigureModelCache, fish_small_model_cache: FigureModelCache, biped_large_model_cache: FigureModelCache, biped_small_model_cache: FigureModelCache, object_model_cache: FigureModelCache, item_drop_model_cache: FigureModelCache, ship_model_cache: FigureModelCache, golem_model_cache: FigureModelCache, volume_model_cache: FigureModelCache, arthropod_model_cache: FigureModelCache, states: FigureMgrStates, } impl FigureMgr { pub fn new(renderer: &mut Renderer) -> Self { Self { col_lights: FigureColLights::new(renderer), model_cache: FigureModelCache::new(), theropod_model_cache: FigureModelCache::new(), quadruped_small_model_cache: FigureModelCache::new(), quadruped_medium_model_cache: FigureModelCache::new(), quadruped_low_model_cache: FigureModelCache::new(), bird_medium_model_cache: FigureModelCache::new(), bird_large_model_cache: FigureModelCache::new(), dragon_model_cache: FigureModelCache::new(), fish_medium_model_cache: FigureModelCache::new(), fish_small_model_cache: FigureModelCache::new(), biped_large_model_cache: FigureModelCache::new(), biped_small_model_cache: FigureModelCache::new(), object_model_cache: FigureModelCache::new(), item_drop_model_cache: FigureModelCache::new(), ship_model_cache: FigureModelCache::new(), golem_model_cache: FigureModelCache::new(), volume_model_cache: FigureModelCache::new(), arthropod_model_cache: FigureModelCache::new(), states: FigureMgrStates::default(), } } pub fn col_lights(&self) -> &FigureColLights { &self.col_lights } fn any_watcher_reloaded(&mut self) -> bool { self.model_cache.watcher_reloaded() || self.theropod_model_cache.watcher_reloaded() || self.quadruped_small_model_cache.watcher_reloaded() || self.quadruped_medium_model_cache.watcher_reloaded() || self.quadruped_low_model_cache.watcher_reloaded() || self.bird_medium_model_cache.watcher_reloaded() || self.bird_large_model_cache.watcher_reloaded() || self.dragon_model_cache.watcher_reloaded() || self.fish_medium_model_cache.watcher_reloaded() || self.fish_small_model_cache.watcher_reloaded() || self.biped_large_model_cache.watcher_reloaded() || self.biped_small_model_cache.watcher_reloaded() || self.object_model_cache.watcher_reloaded() || self.item_drop_model_cache.watcher_reloaded() || self.ship_model_cache.watcher_reloaded() || self.golem_model_cache.watcher_reloaded() || self.volume_model_cache.watcher_reloaded() || self.arthropod_model_cache.watcher_reloaded() } pub fn clean(&mut self, tick: u64) { span!(_guard, "clean", "FigureManager::clean"); if self.any_watcher_reloaded() { self.col_lights.atlas.clear(); self.model_cache.clear_models(); self.theropod_model_cache.clear_models(); self.quadruped_small_model_cache.clear_models(); self.quadruped_medium_model_cache.clear_models(); self.quadruped_low_model_cache.clear_models(); self.bird_medium_model_cache.clear_models(); self.bird_large_model_cache.clear_models(); self.dragon_model_cache.clear_models(); self.fish_medium_model_cache.clear_models(); self.fish_small_model_cache.clear_models(); self.biped_large_model_cache.clear_models(); self.biped_small_model_cache.clear_models(); self.object_model_cache.clear_models(); self.item_drop_model_cache.clear_models(); self.ship_model_cache.clear_models(); self.golem_model_cache.clear_models(); self.volume_model_cache.clear_models(); self.arthropod_model_cache.clear_models(); } self.model_cache.clean(&mut self.col_lights, tick); self.theropod_model_cache.clean(&mut self.col_lights, tick); self.quadruped_small_model_cache .clean(&mut self.col_lights, tick); self.quadruped_medium_model_cache .clean(&mut self.col_lights, tick); self.quadruped_low_model_cache .clean(&mut self.col_lights, tick); self.bird_medium_model_cache .clean(&mut self.col_lights, tick); self.bird_large_model_cache .clean(&mut self.col_lights, tick); self.dragon_model_cache.clean(&mut self.col_lights, tick); self.fish_medium_model_cache .clean(&mut self.col_lights, tick); self.fish_small_model_cache .clean(&mut self.col_lights, tick); self.biped_large_model_cache .clean(&mut self.col_lights, tick); self.biped_small_model_cache .clean(&mut self.col_lights, tick); self.object_model_cache.clean(&mut self.col_lights, tick); self.item_drop_model_cache.clean(&mut self.col_lights, tick); self.ship_model_cache.clean(&mut self.col_lights, tick); self.golem_model_cache.clean(&mut self.col_lights, tick); self.volume_model_cache.clean(&mut self.col_lights, tick); self.arthropod_model_cache.clean(&mut self.col_lights, tick); } pub fn update_lighting(&mut self, scene_data: &SceneData) { span!(_guard, "update_lighting", "FigureManager::update_lighting"); let ecs = scene_data.state.ecs(); for (entity, body, light_emitter) in ( &ecs.entities(), ecs.read_storage::().maybe(), &ecs.read_storage::(), ) .join() { // Add LightAnimation for objects with a LightEmitter let mut anim_storage = ecs.write_storage::(); if anim_storage.get_mut(entity).is_none() { let anim = LightAnimation { offset: body .map(|b| b.default_light_offset()) .unwrap_or_else(Vec3::zero), col: light_emitter.col, strength: 0.0, }; let _ = anim_storage.insert(entity, anim); } } let dt = ecs.fetch::().0; let updater = ecs.read_resource::(); for (entity, light_emitter_opt, interpolated, pos, body, mut light_anim) in ( &ecs.entities(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), &ecs.read_storage::(), ecs.read_storage::().maybe(), &mut ecs.write_storage::(), ) .join() { let (target_col, target_strength, flicker, animated) = if let Some(emitter) = light_emitter_opt { ( emitter.col, if emitter.strength.is_normal() { emitter.strength } else { 0.0 }, emitter.flicker, emitter.animated, ) } else { (Rgb::zero(), 0.0, 0.0, true) }; if let Some(lantern_offset) = body .and_then(|body| self.states.get_mut(body, &entity)) .and_then(|state| { // Calculate the correct lantern position let pos = anim::vek::Vec3::from( interpolated.map(|i| i.pos).unwrap_or(pos.0).into_array(), ); Some( state.mount_world_pos + state.mount_transform.orientation * anim::vek::Vec3::from(state.lantern_offset?.into_array()) - pos, ) }) { light_anim.offset = vek::Vec3::from(lantern_offset); } else if let Some(body) = body { light_anim.offset = body.default_light_offset(); } if !light_anim.strength.is_normal() { light_anim.strength = 0.0; } if animated { let flicker = (rand::random::() - 0.5) * flicker / dt.sqrt(); // Close gap between current and target strength by 95% per second let delta = 0.05_f32.powf(dt); light_anim.strength = light_anim.strength * delta + (target_strength + flicker) * (1.0 - delta); light_anim.col = light_anim.col * delta + target_col * (1.0 - delta) } else { light_anim.strength = target_strength; light_anim.col = target_col; } // NOTE: We add `LIGHT_EPSILON` because if we wait for numbers to become // equal to target (or even within a subnormal), it will take a minimum // of 30 seconds for a light to fully turn off (for initial // strength ≥ 1), which prevents optimizations (particularly those that // can kick in with zero lights). const LIGHT_EPSILON: f32 = 0.0001; if (light_anim.strength - target_strength).abs() < LIGHT_EPSILON { light_anim.strength = target_strength; if light_anim.strength == 0.0 { updater.remove::(entity); } } } } pub fn maintain( &mut self, renderer: &mut Renderer, trail_mgr: &mut TrailMgr, scene_data: &SceneData, // Visible chunk data. visible_psr_bounds: math::Aabr, visible_por_bounds: math::Aabr, camera: &Camera, terrain: Option<&Terrain>, ) -> anim::vek::Aabb { span!(_guard, "maintain", "FigureManager::maintain"); let state = scene_data.state; let time = state.get_time() as f32; let tick = scene_data.tick; let ecs = state.ecs(); let view_distance = scene_data.entity_view_distance; let dt = state.get_delta_time(); let dt_lerp = (15.0 * dt).min(1.0); let frustum = camera.frustum(); // Sun shadows--find the bounding box of the shadow map plane (i.e. the bounds // of the image rendered from the light). If the position projected // with the ray_mat matrix is valid, and shadows are otherwise enabled, // we mark can_shadow. // Rain occlusion is very similar to sun shadows, but using a different ray_mat, // and only if it's raining. let (can_shadow_sun, can_occlude_rain) = { let Dependents { proj_mat: _, view_mat: _, cam_pos, .. } = camera.dependents(); let sun_dir = scene_data.get_sun_dir(); let is_daylight = sun_dir.z < 0.0/*0.6*/; // Are shadows enabled at all? let can_shadow_sun = renderer.pipeline_modes().shadow.is_map() && is_daylight; let weather = scene_data.state.weather_at(cam_pos.xy()); let cam_pos = math::Vec3::from(cam_pos); let focus_off = math::Vec3::from(camera.get_focus_pos().map(f32::trunc)); let focus_off_mat = math::Mat4::translation_3d(-focus_off); let collides_with_aabr = |a: math::Aabr, b: math::Aabr| { let min = math::Vec4::new(a.min.x, a.min.y, b.min.x, b.min.y); let max = math::Vec4::new(b.max.x, b.max.y, a.max.x, a.max.y); #[cfg(feature = "simd")] return min.partial_cmple_simd(max).reduce_and(); #[cfg(not(feature = "simd"))] return min.partial_cmple(&max).reduce_and(); }; let can_shadow = |ray_direction: Vec3, enabled: bool, visible_bounds: math::Aabr| { let ray_direction = math::Vec3::from(ray_direction); // Transform (semi) world space to light space. let ray_mat: math::Mat4 = math::Mat4::look_at_rh(cam_pos, cam_pos + ray_direction, math::Vec3::unit_y()); let ray_mat = ray_mat * focus_off_mat; move |pos: (anim::vek::Vec3,), radius: f32| { // Short circuit when there are no shadows to cast. if !enabled { return false; } // First project center onto shadow map. let center = (ray_mat * math::Vec4::new(pos.0.x, pos.0.y, pos.0.z, 1.0)).xy(); // Then, create an approximate bounding box (± radius). let figure_box = math::Aabr { min: center - radius, max: center + radius, }; // Quick intersection test for membership in the PSC (potential shader caster) // list. collides_with_aabr(figure_box, visible_bounds) } }; ( can_shadow(sun_dir, can_shadow_sun, visible_psr_bounds), can_shadow( weather.rain_vel(), weather.rain > RAIN_THRESHOLD, visible_por_bounds, ), ) }; // Get player position. let player_pos = ecs .read_storage::() .get(scene_data.viewpoint_entity) .map_or(anim::vek::Vec3::zero(), |pos| anim::vek::Vec3::from(pos.0)); let visible_aabb = anim::vek::Aabb { min: player_pos - 2.0, max: player_pos + 2.0, }; let camera_mode = camera.get_mode(); let character_state_storage = state.read_storage::(); let slow_jobs = state.slow_job_pool(); let character_state = character_state_storage.get(scene_data.viewpoint_entity); let focus_pos = anim::vek::Vec3::::from(camera.get_focus_pos()); let mut update_buf = [Default::default(); anim::MAX_BONE_COUNT]; let uid_allocator = ecs.read_resource::(); let bodies = ecs.read_storage::(); let terrain_grid = ecs.read_resource::(); for ( i, ( entity, pos, controller, interpolated, vel, scale, body, character, last_character, physics, health, inventory, item, light_emitter, is_rider, collider, ), ) in ( &ecs.entities(), &ecs.read_storage::(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), &ecs.read_storage::(), ecs.read_storage::().maybe(), &ecs.read_storage::(), ecs.read_storage::().maybe(), ecs.read_storage::>().maybe(), &ecs.read_storage::(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::().maybe(), ecs.read_storage::>().maybe(), ecs.read_storage::().maybe(), ) .join() .enumerate() { // Velocity relative to the current ground let rel_vel = anim::vek::Vec3::::from(vel.0 - physics.ground_vel); let look_dir = controller.map(|c| c.inputs.look_dir).unwrap_or_default(); let is_viewpoint = scene_data.viewpoint_entity == entity; let viewpoint_camera_mode = if is_viewpoint { camera_mode } else { CameraMode::default() }; let viewpoint_character_state = if is_viewpoint { character_state } else { None }; let (pos, ori) = interpolated .map(|i| { ( (anim::vek::Vec3::from(i.pos),), anim::vek::Quaternion::::from(i.ori), ) }) .unwrap_or(( (anim::vek::Vec3::::from(pos.0),), anim::vek::Quaternion::::default(), )); let wall_dir = physics.on_wall.map(anim::vek::Vec3::from); // Maintaining figure data and sending new figure data to the GPU turns out to // be a very expensive operation. We want to avoid doing it as much // as possible, so we make the assumption that players don't care so // much about the update *rate* for far away things. As the entity // goes further and further away, we start to 'skip' update ticks. // TODO: Investigate passing the velocity into the shader so we can at least // interpolate motion const MIN_PERFECT_RATE_DIST: f32 = 100.0; if (i as u64 + tick) % (((pos.0.distance_squared(focus_pos).powf(0.25) - MIN_PERFECT_RATE_DIST.sqrt()) .max(0.0) / 3.0) as u64) .saturating_add(1) != 0 { continue; } // Check whether we could have been shadowing last frame. let mut state = self.states.get_mut(body, &entity); let can_shadow_prev = state .as_mut() .map(|state| state.can_shadow_sun()) .unwrap_or(false); // Don't process figures outside the vd let vd_frac = anim::vek::Vec2::from(pos.0 - player_pos) .map2( anim::vek::Vec2::::from(TerrainChunk::RECT_SIZE), |d: f32, sz| d.abs() as f32 / sz as f32, ) .magnitude() / view_distance as f32; // Keep from re-adding/removing entities on the border of the vd if vd_frac > 1.2 { self.states.remove(body, &entity); continue; } else if vd_frac > 1.0 { state.as_mut().map(|state| state.visible = false); // Keep processing if this might be a shadow caster. // NOTE: Not worth to do for rain_occlusion, since that only happens in closeby // chunks. if !can_shadow_prev { continue; } } // Don't display figures outside the frustum spectrum (this is important to do // for any figure that potentially casts a shadow, since we use this // to estimate bounds for shadow maps). Currently, we don't do this before the // update cull, so it's possible that faraway figures will not // shadow correctly until their next update. For now, we treat this // as an acceptable tradeoff. let radius = scale.unwrap_or(&Scale(1.0)).0 * 2.0; let (in_frustum, lpindex) = if let Some(ref mut meta) = state { let (in_frustum, lpindex) = BoundingSphere::new(pos.0.into_array(), radius) .coherent_test_against_frustum(frustum, meta.lpindex); let in_frustum = in_frustum || matches!(body, Body::Ship(_)) || pos.0.distance_squared(focus_pos) < 32.0f32.powi(2); meta.visible = in_frustum; meta.lpindex = lpindex; if in_frustum { /* // Update visible bounds. visible_aabb.expand_to_contain(Aabb { min: pos.0 - radius, max: pos.0 + radius, }); */ } else { // Check whether we can shadow. meta.can_shadow_sun = can_shadow_sun(pos, radius); meta.can_occlude_rain = can_occlude_rain(pos, radius); } (in_frustum, lpindex) } else { (true, 0) }; // Change in health as color! let col = health .map(|h| { let time = scene_data.state.ecs().read_resource::