veloren_voxygen/scene/
mod.rs

1pub mod camera;
2pub mod debug;
3pub mod figure;
4pub mod lod;
5pub mod math;
6pub mod particle;
7pub mod simple;
8pub mod smoke_cycle;
9pub mod terrain;
10pub mod tether;
11pub mod trail;
12
13use std::collections::HashSet;
14
15pub use self::{
16    camera::{Camera, CameraMode},
17    debug::{Debug, DebugShape, DebugShapeId},
18    figure::FigureMgr,
19    lod::Lod,
20    particle::ParticleMgr,
21    terrain::{SpriteRenderContextLazy, Terrain},
22    tether::TetherMgr,
23    trail::TrailMgr,
24};
25use crate::{
26    audio::{
27        AudioFrontend,
28        ambience::{self, AmbienceMgr},
29        music::MusicMgr,
30        sfx::SfxMgr,
31    },
32    render::{
33        CloudsLocals, Consts, CullingMode, Drawer, GlobalModel, Globals, GlobalsBindGroup, Light,
34        Model, PointLightMatrix, PostProcessLocals, RainOcclusionLocals, Renderer, Shadow,
35        ShadowLocals, SkyboxVertex, create_skybox_mesh,
36    },
37    session::PlayerDebugLines,
38    settings::Settings,
39    window::{AnalogGameInput, Event},
40};
41use client::Client;
42use common::{
43    calendar::Calendar,
44    comp::{
45        self, CharacterState, item::ItemDesc, ship::figuredata::VOXEL_COLLIDER_MANIFEST,
46        slot::EquipSlot, tool::ToolKind,
47    },
48    outcome::Outcome,
49    resources::{DeltaTime, TimeOfDay, TimeScale},
50    terrain::{BlockKind, TerrainChunk, TerrainGrid},
51    vol::ReadVol,
52    weather::WeatherGrid,
53};
54use common_base::{prof_span, span};
55use common_state::State;
56use comp::item::Reagent;
57use hashbrown::HashMap;
58use num::traits::{Float, FloatConst};
59use specs::{Entity as EcsEntity, Join, LendJoin, WorldExt};
60use vek::*;
61
62const ZOOM_CAP_PLAYER: f32 = 1000.0;
63const ZOOM_CAP_ADMIN: f32 = 100000.0;
64
65// TODO: Don't hard-code this.
66const CURSOR_PAN_SCALE: f32 = 0.005;
67
68pub(crate) const MAX_LIGHT_COUNT: usize = 20; // 31 (total shadow_mats is limited to 128 with default
69// max_uniform_buffer_binding_size)
70pub(crate) const MAX_SHADOW_COUNT: usize = 24;
71pub(crate) const MAX_POINT_LIGHT_MATRICES_COUNT: usize = MAX_LIGHT_COUNT * 6 + 6;
72const NUM_DIRECTED_LIGHTS: usize = 1;
73const LIGHT_DIST_RADIUS: f32 = 64.0; // The distance beyond which lights may not emit light from their origin
74const SHADOW_DIST_RADIUS: f32 = 8.0;
75const SHADOW_MAX_DIST: f32 = 96.0; // The distance beyond which shadows may not be visible
76/// The minimum sin γ we will use before switching to uniform mapping.
77const EPSILON_UPSILON: f64 = -1.0;
78
79const SHADOW_NEAR: f32 = 0.25; // Near plane for shadow map point light rendering.
80const SHADOW_FAR: f32 = 128.0; // Far plane for shadow map point light rendering.
81
82/// Above this speed is considered running
83/// Used for first person camera effects
84const RUNNING_THRESHOLD: f32 = 0.7;
85
86/// The threashold for starting calculations with rain.
87const RAIN_THRESHOLD: f32 = 0.0;
88
89/// is_daylight, array of active lights.
90pub type LightData<'a> = (bool, &'a [Light]);
91
92struct EventLight {
93    light: Light,
94    timeout: f32,
95    fadeout: fn(f32) -> f32,
96}
97
98struct Skybox {
99    model: Model<SkyboxVertex>,
100}
101
102pub struct Scene {
103    data: GlobalModel,
104    globals_bind_group: GlobalsBindGroup,
105    camera: Camera,
106    camera_input_state: Vec2<f32>,
107    event_lights: Vec<EventLight>,
108
109    skybox: Skybox,
110    terrain: Terrain<TerrainChunk>,
111    pub debug: Debug,
112    pub lod: Lod,
113    loaded_distance: f32,
114    /// x coordinate is sea level (minimum height for any land chunk), and y
115    /// coordinate is the maximum height above the mnimimum for any land
116    /// chunk.
117    map_bounds: Vec2<f32>,
118    select_pos: Option<Vec3<i32>>,
119    light_data: Vec<Light>,
120
121    particle_mgr: ParticleMgr,
122    trail_mgr: TrailMgr,
123    figure_mgr: FigureMgr,
124    tether_mgr: TetherMgr,
125    pub sfx_mgr: SfxMgr,
126    pub music_mgr: MusicMgr,
127    ambience_mgr: AmbienceMgr,
128
129    integrated_rain_vel: f32,
130    wind_vel: Vec2<f32>,
131    pub interpolated_time_of_day: Option<f64>,
132    last_lightning: Option<(Vec3<f32>, f64)>,
133    local_time: f64,
134
135    pub debug_vectors_enabled: bool,
136}
137
138pub struct SceneData<'a> {
139    pub client: &'a Client,
140    pub state: &'a State,
141    pub viewpoint_entity: specs::Entity,
142    pub mutable_viewpoint: bool,
143    pub target_entities: &'a HashSet<specs::Entity>,
144    pub loaded_distance: f32,
145    pub terrain_view_distance: u32, // not used currently
146    pub entity_view_distance: u32,
147    pub tick: u64,
148    pub gamma: f32,
149    pub exposure: f32,
150    pub ambiance: f32,
151    pub mouse_smoothing: bool,
152    pub sprite_render_distance: f32,
153    pub particles_enabled: bool,
154    pub weapon_trails_enabled: bool,
155    pub flashing_lights_enabled: bool,
156    pub figure_lod_render_distance: f32,
157    pub is_aiming: bool,
158    pub interpolated_time_of_day: Option<f64>,
159}
160
161impl SceneData<'_> {
162    pub fn get_sun_dir(&self) -> Vec3<f32> {
163        TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_sun_dir()
164    }
165
166    pub fn get_moon_dir(&self) -> Vec3<f32> {
167        TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_moon_dir()
168    }
169}
170
171/// Approximate a scalar field of view angle using the parameterization from
172/// section 4.3 of Lloyd's thesis:
173///
174/// W_e = 2 n_e tan θ
175///
176/// where
177///
178/// W_e = 2 is the width of the image plane (for our projections, since they go
179/// from -1 to 1) n_e = near_plane is the near plane for the view frustum
180/// θ = (fov / 2) is the half-angle of the FOV (the one passed to
181/// Mat4::projection_rh_zo).
182///
183/// Although the widths for the x and y image planes are the same, they are
184/// different in this framework due to the introduction of an aspect ratio:
185///
186/// y'(p) = 1.0 / tan(fov / 2) * p.y / -p.z
187/// x'(p) = 1.0 / (aspect * tan(fov / 2)) * p.x / -p.z
188///
189/// i.e.
190///
191/// y'(x, y, -near, w) = 1 / tan(fov / 2) p.y / near
192/// x'(x, y, -near, w) = 1 / (aspect * tan(fov / 2)) p.x / near
193///
194/// W_e,y = 2 * near_plane * tan(fov / 2)
195/// W_e,x = 2 * near_plane * aspect * W_e,y
196///
197/// Θ_x = atan(W_e_y / 2 / near_plane) = atanfov / t()
198///
199/// i.e. we have an "effective" W_e_x of
200///
201/// 2 = 2 * near_plane * tan Θ
202///
203/// atan(1 / near_plane) = θ
204///
205/// y'
206/// x(-near)
207/// W_e = 2 * near_plane *
208///
209/// W_e_y / n_e = tan (fov / 2)
210/// W_e_x = 2 n
211fn compute_scalar_fov<F: Float>(_near_plane: F, fov: F, aspect: F) -> F {
212    let two = F::one() + F::one();
213    let theta_y = fov / two;
214    let theta_x = (aspect * theta_y.tan()).atan();
215    theta_x.min(theta_y)
216}
217
218/// Compute a near-optimal warping parameter that helps minimize error in a
219/// shadow map.
220///
221/// See section 5.2 of Brandon Lloyd's thesis:
222///
223/// [http://gamma.cs.unc.edu/papers/documents/dissertations/lloyd07.pdf](Logarithmic Perspective Shadow Maps).
224///
225/// η =
226///     0                                                         γ < γ_a
227///     -1 + (η_b + 1)(1 + cos(90 (γ - γ_a)/(γ_b - γ_a)))   γ_a ≤ γ < γ_b
228///     η_b + (η_c - η_b)  sin(90 (γ - γ_b)/(γ_c - γ_b))    γ_b ≤ γ < γ_c
229///     η_c                                                 γ_c ≤ γ
230///
231/// NOTE: Equation's described behavior is *wrong!*  I have pieced together a
232/// slightly different function that seems to more closely satisfy the author's
233/// intent:
234///
235/// η =
236///     -1                                                        γ < γ_a
237///     -1 + (η_b + 1)            (γ - γ_a)/(γ_b - γ_a)     γ_a ≤ γ < γ_b
238///     η_b + (η_c - η_b)  sin(90 (γ - γ_b)/(γ_c - γ_b))    γ_b ≤ γ < γ_c
239///     η_c                                                 γ_c ≤ γ
240///
241/// There are other alternatives that may have more desirable properties, such
242/// as:
243///
244/// η =
245///     -1                                                        γ < γ_a
246///     -1 + (η_b + 1)(1 - cos(90 (γ - γ_a)/(γ_b - γ_a)))   γ_a ≤ γ < γ_b
247///     η_b + (η_c - η_b)  sin(90 (γ - γ_b)/(γ_c - γ_b))    γ_b ≤ γ < γ_c
248///     η_c                                                 γ_c ≤ γ
249fn compute_warping_parameter<F: Float + FloatConst>(
250    gamma: F,
251    (gamma_a, gamma_b, gamma_c): (F, F, F),
252    (eta_b, eta_c): (F, F),
253) -> F {
254    if gamma < gamma_a {
255        -F::one()
256        /* F::zero() */
257    } else if gamma_a <= gamma && gamma < gamma_b {
258        /* -F::one() + (eta_b + F::one()) * (F::one() + (F::FRAC_PI_2() * (gamma - gamma_a) / (gamma_b - gamma_a)).cos()) */
259        -F::one() + (eta_b + F::one()) * (F::one() - (F::FRAC_PI_2() * (gamma - gamma_a) / (gamma_b - gamma_a)).cos())
260        // -F::one() + (eta_b + F::one()) * ((gamma - gamma_a) / (gamma_b - gamma_a))
261    } else if gamma_b <= gamma && gamma < gamma_c {
262        eta_b + (eta_c - eta_b) * (F::FRAC_PI_2() * (gamma - gamma_b) / (gamma_c - gamma_b)).sin()
263    } else {
264        eta_c
265    }
266    // NOTE: Just in case we go out of range due to floating point imprecision.
267    .max(-F::one()).min(F::one())
268}
269
270/// Compute a near-optimal warping parameter that falls off quickly enough
271/// when the warp angle goes past the minimum field of view angle, for
272/// perspective projections.
273///
274/// For F_p (perspective warping) and view fov angle θ,the parameters are:
275///
276/// γ_a = θ / 3
277/// γ_b = θ
278/// γ_c = θ + 0.3(90 - θ)
279///
280/// η_b = -0.2
281/// η_c = 0
282///
283/// See compute_warping_parameter.
284fn compute_warping_parameter_perspective<F: Float + FloatConst>(
285    gamma: F,
286    near_plane: F,
287    fov: F,
288    aspect: F,
289) -> F {
290    let theta = compute_scalar_fov(near_plane, fov, aspect);
291    let two = F::one() + F::one();
292    let three = two + F::one();
293    let ten = three + three + three + F::one();
294    compute_warping_parameter(
295        gamma,
296        (
297            theta / three,
298            theta,
299            theta + (three / ten) * (F::FRAC_PI_2() - theta),
300        ),
301        (-two / ten, F::zero()),
302    )
303}
304
305impl Scene {
306    /// Create a new `Scene` with default parameters.
307    pub fn new(
308        renderer: &mut Renderer,
309        lazy_init: &mut SpriteRenderContextLazy,
310        client: &Client,
311        settings: &Settings,
312    ) -> Self {
313        let resolution = renderer.resolution().map(|e| e as f32);
314        let sprite_render_context = lazy_init(renderer);
315
316        let data = GlobalModel {
317            globals: renderer.create_consts(&[Globals::default()]),
318            lights: renderer.create_consts(&[Light::default(); MAX_LIGHT_COUNT]),
319            shadows: renderer.create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]),
320            shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]),
321            rain_occlusion_mats: renderer
322                .create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]),
323            point_light_matrices: Box::new(
324                [PointLightMatrix::default(); MAX_POINT_LIGHT_MATRICES_COUNT],
325            ),
326        };
327
328        let lod = Lod::new(renderer, client, settings);
329
330        let globals_bind_group = renderer.bind_globals(&data, lod.get_data());
331
332        let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
333
334        let camera_mode = match client.presence() {
335            Some(comp::PresenceKind::Spectator) => CameraMode::Freefly,
336            _ => CameraMode::ThirdPerson,
337        };
338
339        let calendar = client.state().ecs().read_resource::<Calendar>();
340
341        Self {
342            data,
343            globals_bind_group,
344            camera: Camera::new(resolution.x / resolution.y, camera_mode),
345            camera_input_state: Vec2::zero(),
346            event_lights: Vec::new(),
347
348            skybox: Skybox {
349                model: renderer.create_model(&create_skybox_mesh()).unwrap(),
350            },
351            terrain,
352            debug: Debug::new(),
353            lod,
354            loaded_distance: 0.0,
355            map_bounds: Vec2::new(
356                client.world_data().min_chunk_alt(),
357                client.world_data().max_chunk_alt(),
358            ),
359            select_pos: None,
360            light_data: Vec::new(),
361            particle_mgr: ParticleMgr::new(renderer),
362            trail_mgr: TrailMgr::default(),
363            figure_mgr: FigureMgr::new(renderer),
364            tether_mgr: TetherMgr::new(renderer),
365            sfx_mgr: SfxMgr::default(),
366            music_mgr: MusicMgr::new(&calendar),
367            ambience_mgr: AmbienceMgr {
368                ambience: ambience::load_ambience_items(),
369            },
370            integrated_rain_vel: 0.0,
371            wind_vel: Vec2::zero(),
372            interpolated_time_of_day: None,
373            last_lightning: None,
374            local_time: 0.0,
375            debug_vectors_enabled: false,
376        }
377    }
378
379    /// Get a reference to the scene's globals.
380    pub fn globals(&self) -> &Consts<Globals> { &self.data.globals }
381
382    /// Get a reference to the scene's camera.
383    pub fn camera(&self) -> &Camera { &self.camera }
384
385    /// Get a reference to the scene's terrain.
386    pub fn terrain(&self) -> &Terrain<TerrainChunk> { &self.terrain }
387
388    /// Get a reference to the scene's lights.
389    pub fn lights(&self) -> &Vec<Light> { &self.light_data }
390
391    /// Get a reference to the scene's particle manager.
392    pub fn particle_mgr(&self) -> &ParticleMgr { &self.particle_mgr }
393
394    /// Get a reference to the scene's trail manager.
395    pub fn trail_mgr(&self) -> &TrailMgr { &self.trail_mgr }
396
397    /// Get a reference to the scene's figure manager.
398    pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr }
399
400    pub fn music_mgr(&self) -> &MusicMgr { &self.music_mgr }
401
402    /// Get a mutable reference to the scene's camera.
403    pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera }
404
405    /// Set the block position that the player is interacting with
406    pub fn set_select_pos(&mut self, pos: Option<Vec3<i32>>) { self.select_pos = pos; }
407
408    pub fn select_pos(&self) -> Option<Vec3<i32>> { self.select_pos }
409
410    /// Handle an incoming user input event (e.g.: cursor moved, key pressed,
411    /// window closed).
412    ///
413    /// If the event is handled, return true.
414    pub fn handle_input_event(&mut self, event: Event, client: &Client) -> bool {
415        match event {
416            // When the window is resized, change the camera's aspect ratio
417            Event::Resize(dims) => {
418                self.camera.set_aspect_ratio(dims.x as f32 / dims.y as f32);
419                true
420            },
421            // Panning the cursor makes the camera rotate
422            Event::CursorPan(delta) => {
423                self.camera.rotate_by(Vec3::from(delta) * CURSOR_PAN_SCALE);
424                true
425            },
426            // Zoom the camera when a zoom event occurs
427            Event::Zoom(delta) => {
428                let cap = if client.is_moderator() {
429                    ZOOM_CAP_ADMIN
430                } else {
431                    ZOOM_CAP_PLAYER
432                };
433                // when zooming in the distance the camera travelles should be based on the
434                // final distance. This is to make sure the camera travelles the
435                // same distance when zooming in and out
436                let player_scale = client
437                    .state()
438                    .read_component_copied::<comp::Scale>(client.entity())
439                    .map_or(1.0, |s| s.0);
440                if delta < 0.0 {
441                    self.camera.zoom_switch(
442                        // Thank you Imbris for doing the math
443                        delta * (0.05 + self.camera.get_distance() * 0.01) / (1.0 - delta * 0.01),
444                        cap,
445                        player_scale,
446                    );
447                } else {
448                    self.camera.zoom_switch(
449                        delta * (0.05 + self.camera.get_distance() * 0.01),
450                        cap,
451                        player_scale,
452                    );
453                }
454                true
455            },
456            Event::AnalogGameInput(input) => match input {
457                AnalogGameInput::CameraX(d) => {
458                    self.camera_input_state.x = d;
459                    true
460                },
461                AnalogGameInput::CameraY(d) => {
462                    self.camera_input_state.y = d;
463                    true
464                },
465                _ => false,
466            },
467            // All other events are unhandled
468            _ => false,
469        }
470    }
471
472    pub fn handle_outcome(
473        &mut self,
474        outcome: &Outcome,
475        scene_data: &SceneData,
476        audio: &mut AudioFrontend,
477    ) {
478        span!(_guard, "handle_outcome", "Scene::handle_outcome");
479        self.particle_mgr
480            .handle_outcome(outcome, scene_data, &self.figure_mgr);
481        self.sfx_mgr
482            .handle_outcome(outcome, audio, scene_data.client);
483
484        match outcome {
485            Outcome::Lightning { pos } => {
486                self.last_lightning = Some((*pos, scene_data.state.get_time()));
487            },
488            Outcome::Explosion {
489                pos,
490                power,
491                is_attack,
492                reagent,
493                ..
494            } => self.event_lights.push(EventLight {
495                light: Light::new(
496                    *pos,
497                    match reagent {
498                        Some(Reagent::Blue) => Rgb::new(0.15, 0.4, 1.0),
499                        Some(Reagent::Green) => Rgb::new(0.0, 1.0, 0.0),
500                        Some(Reagent::Purple) => Rgb::new(0.7, 0.0, 1.0),
501                        Some(Reagent::Red) => {
502                            if *is_attack {
503                                Rgb::new(1.0, 0.5, 0.0)
504                            } else {
505                                Rgb::new(1.0, 0.0, 0.0)
506                            }
507                        },
508                        Some(Reagent::White) => Rgb::new(1.0, 1.0, 1.0),
509                        Some(Reagent::Yellow) => Rgb::new(1.0, 1.0, 0.0),
510                        Some(Reagent::FireRain) => Rgb::new(1.0, 0.8, 0.3),
511                        Some(Reagent::FireGigas) => Rgb::new(1.0, 0.6, 0.2),
512                        None => Rgb::new(1.0, 0.5, 0.0),
513                    },
514                    power
515                        * if *is_attack || reagent.is_none() {
516                            2.5
517                        } else {
518                            5.0
519                        },
520                ),
521                timeout: match reagent {
522                    Some(_) => 1.0,
523                    None => 0.5,
524                },
525                fadeout: |timeout| timeout * 2.0,
526            }),
527            Outcome::ProjectileShot { .. } => {},
528            _ => {},
529        }
530    }
531
532    /// Maintain data such as GPU constant buffers, models, etc. To be called
533    /// once per tick.
534    pub fn maintain(
535        &mut self,
536        renderer: &mut Renderer,
537        audio: &mut AudioFrontend,
538        scene_data: &SceneData,
539        client: &Client,
540        settings: &Settings,
541    ) {
542        span!(_guard, "maintain", "Scene::maintain");
543        // Get player position.
544        let ecs = scene_data.state.ecs();
545
546        let dt = ecs.fetch::<DeltaTime>().0;
547
548        self.local_time += dt as f64 * ecs.fetch::<TimeScale>().0;
549
550        let positions = ecs.read_storage::<comp::Pos>();
551
552        let viewpoint_pos = if let Some(viewpoint_pos) =
553            positions.get(scene_data.viewpoint_entity).map(|pos| pos.0)
554        {
555            let viewpoint_ori = ecs
556                .read_storage::<comp::Ori>()
557                .get(scene_data.viewpoint_entity)
558                .map_or(Quaternion::identity(), |ori| ori.to_quat());
559
560            let viewpoint_look_ori = ecs
561                .read_storage::<comp::CharacterActivity>()
562                .get(scene_data.viewpoint_entity)
563                .and_then(|activity| activity.look_dir)
564                .map(|dir| {
565                    let d = dir.to_vec();
566
567                    let pitch = (-d.z).asin();
568                    let yaw = d.x.atan2(d.y);
569
570                    Vec3::new(yaw, pitch, 0.0)
571                })
572                .unwrap_or_else(|| {
573                    let q = viewpoint_ori;
574                    let sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z);
575                    let cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
576                    let pitch = sinr_cosp.atan2(cosr_cosp);
577
578                    let siny_cosp = 2.0 * (q.w * q.z + q.x * q.y);
579                    let cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
580                    let yaw = siny_cosp.atan2(cosy_cosp);
581
582                    Vec3::new(-yaw, -pitch, 0.0)
583                });
584
585            let viewpoint_scale = ecs
586                .read_storage::<comp::Scale>()
587                .get(scene_data.viewpoint_entity)
588                .map_or(1.0, |scale| scale.0);
589
590            let (is_humanoid, viewpoint_height, viewpoint_eye_height) = ecs
591                .read_storage::<comp::Body>()
592                .get(scene_data.viewpoint_entity)
593                .map_or((false, 1.0, 0.0), |b| {
594                    (
595                        matches!(b, comp::Body::Humanoid(_)),
596                        b.height(),
597                        b.eye_height(1.0), // Scale is applied later
598                    )
599                });
600
601            if scene_data.mutable_viewpoint || matches!(self.camera.get_mode(), CameraMode::Freefly)
602            {
603                // Add the analog input to camera if it's a mutable viewpoint
604                self.camera
605                    .rotate_by(Vec3::from([self.camera_input_state.x, 0.0, 0.0]));
606                self.camera
607                    .rotate_by(Vec3::from([0.0, self.camera_input_state.y, 0.0]));
608            } else {
609                // Otherwise set the cameras rotation to the viewpoints
610                self.camera.set_orientation(viewpoint_look_ori);
611            }
612
613            let viewpoint_offset = if is_humanoid {
614                let viewpoint_rolling = ecs
615                    .read_storage::<comp::CharacterState>()
616                    .get(scene_data.viewpoint_entity)
617                    .is_some_and(|cs| cs.is_dodge());
618
619                let is_running = ecs
620                    .read_storage::<comp::Vel>()
621                    .get(scene_data.viewpoint_entity)
622                    .zip(
623                        ecs.read_storage::<comp::PhysicsState>()
624                            .get(scene_data.viewpoint_entity),
625                    )
626                    .map(|(v, ps)| {
627                        (v.0 - ps.ground_vel).magnitude_squared() > RUNNING_THRESHOLD.powi(2)
628                    })
629                    .unwrap_or(false);
630
631                let on_ground = ecs
632                    .read_storage::<comp::PhysicsState>()
633                    .get(scene_data.viewpoint_entity)
634                    .map(|p| p.on_ground.is_some());
635
636                let player_entity = client.entity();
637                let holding_ranged = client
638                    .inventories()
639                    .get(player_entity)
640                    .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
641                    .and_then(|item| item.tool_info())
642                    .is_some_and(|tool_kind| {
643                        matches!(
644                            tool_kind,
645                            ToolKind::Bow
646                                | ToolKind::Staff
647                                | ToolKind::Sceptre
648                                | ToolKind::Throwable
649                        )
650                    })
651                    || client
652                        .current::<CharacterState>()
653                        .is_some_and(|char_state| matches!(char_state, CharacterState::Throw(_)));
654
655                let up = match self.camera.get_mode() {
656                    CameraMode::FirstPerson => {
657                        if viewpoint_rolling {
658                            viewpoint_height * 0.42
659                        } else if is_running && on_ground.unwrap_or(false) {
660                            viewpoint_eye_height
661                                + (scene_data.state.get_time() as f32 * 17.0).sin() * 0.05
662                        } else {
663                            viewpoint_eye_height
664                        }
665                    },
666                    CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => {
667                        viewpoint_height * 1.16 + settings.gameplay.aim_offset_y
668                    },
669                    CameraMode::ThirdPerson if scene_data.is_aiming => viewpoint_height * 1.16,
670                    CameraMode::ThirdPerson => viewpoint_eye_height,
671                    CameraMode::Freefly => 0.0,
672                };
673
674                let right = match self.camera.get_mode() {
675                    CameraMode::FirstPerson => 0.0,
676                    CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => {
677                        settings.gameplay.aim_offset_x
678                    },
679                    CameraMode::ThirdPerson => 0.0,
680                    CameraMode::Freefly => 0.0,
681                };
682
683                // Alter camera position to match player.
684                let tilt = self.camera.get_orientation().y;
685                let dist = self.camera.get_distance();
686
687                Vec3::unit_z() * (up * viewpoint_scale - tilt.min(0.0).sin() * dist * 0.6)
688                    + self.camera.right() * (right * viewpoint_scale)
689            } else {
690                self.figure_mgr
691                    .viewpoint_offset(scene_data, scene_data.viewpoint_entity)
692            };
693
694            match self.camera.get_mode() {
695                CameraMode::FirstPerson | CameraMode::ThirdPerson => {
696                    self.camera.set_focus_pos(viewpoint_pos + viewpoint_offset);
697                },
698                CameraMode::Freefly => {},
699            };
700
701            // Tick camera for interpolation.
702            self.camera
703                .update(scene_data.state.get_time(), dt, scene_data.mouse_smoothing);
704            viewpoint_pos
705        } else {
706            Vec3::zero()
707        };
708
709        // Compute camera matrices.
710        self.camera.compute_dependents(&scene_data.state.terrain());
711        let camera::Dependents {
712            view_mat,
713            view_mat_inv,
714            proj_mat,
715            proj_mat_inv,
716            cam_pos,
717            ..
718        } = self.camera.dependents();
719
720        // Update chunk loaded distance smoothly for nice shader fog
721        let loaded_distance =
722            (0.98 * self.loaded_distance + 0.02 * scene_data.loaded_distance).max(0.01);
723
724        // Reset lights ready for the next tick
725        let lights = &mut self.light_data;
726        lights.clear();
727
728        // Maintain the particles.
729        self.particle_mgr.maintain(
730            renderer,
731            scene_data,
732            &self.terrain,
733            &self.figure_mgr,
734            lights,
735        );
736
737        // Maintain the trails.
738        self.trail_mgr.maintain(renderer, scene_data);
739
740        // Update light constants
741        let max_light_dist = loaded_distance.powi(2) + LIGHT_DIST_RADIUS;
742        lights.extend(
743            (
744                &scene_data.state.ecs().read_storage::<comp::Pos>(),
745                scene_data
746                    .state
747                    .ecs()
748                    .read_storage::<crate::ecs::comp::Interpolated>()
749                    .maybe(),
750                &scene_data
751                    .state
752                    .ecs()
753                    .read_storage::<comp::LightAnimation>(),
754                scene_data
755                    .state
756                    .ecs()
757                    .read_storage::<comp::Health>()
758                    .maybe(),
759            )
760                .join()
761                .filter(|(pos, _, light_anim, h)| {
762                    light_anim.col != Rgb::zero()
763                        && light_anim.strength > 0.0
764                        && pos.0.distance_squared(viewpoint_pos) < max_light_dist
765                        && h.is_none_or(|h| !h.is_dead)
766                })
767                .map(|(pos, interpolated, light_anim, _)| {
768                    // Use interpolated values if they are available
769                    let pos = interpolated.map_or(pos.0, |i| i.pos);
770                    Light::new(pos + light_anim.offset, light_anim.col, light_anim.strength)
771                })
772                .chain(
773                    self.event_lights
774                        .iter()
775                        .map(|el| el.light.with_strength((el.fadeout)(el.timeout))),
776                ),
777        );
778        let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
779        let figure_mgr = &self.figure_mgr;
780        lights.extend(
781            (
782                &scene_data.state.ecs().entities(),
783                &scene_data
784                    .state
785                    .read_storage::<crate::ecs::comp::Interpolated>(),
786                &scene_data.state.read_storage::<comp::Body>(),
787                &scene_data.state.read_storage::<comp::Collider>(),
788            )
789                .join()
790                .filter_map(|(entity, interpolated, body, collider)| {
791                    let vol = collider.get_vol(&voxel_colliders_manifest)?;
792                    let (blocks_of_interest, offset) =
793                        figure_mgr.get_blocks_of_interest(entity, body, Some(collider))?;
794
795                    let mat = Mat4::from(interpolated.ori.to_quat())
796                        .translated_3d(interpolated.pos)
797                        * Mat4::translation_3d(offset);
798
799                    let p = mat.inverted().mul_point(viewpoint_pos);
800                    let aabb = Aabb {
801                        min: Vec3::zero(),
802                        max: vol.volume().sz.as_(),
803                    };
804                    if aabb.contains_point(p) || aabb.distance_to_point(p) < max_light_dist {
805                        Some(
806                            blocks_of_interest
807                                .lights
808                                .iter()
809                                .map(move |(block_offset, level)| {
810                                    let wpos = mat.mul_point(block_offset.as_() + 0.5);
811                                    (wpos, level)
812                                })
813                                .filter(move |(wpos, _)| {
814                                    wpos.distance_squared(viewpoint_pos) < max_light_dist
815                                })
816                                .map(|(wpos, level)| {
817                                    Light::new(wpos, Rgb::white(), *level as f32 / 7.0)
818                                }),
819                        )
820                    } else {
821                        None
822                    }
823                })
824                .flatten(),
825        );
826        lights.sort_by_key(|light| light.get_pos().distance_squared(viewpoint_pos) as i32);
827        lights.truncate(MAX_LIGHT_COUNT);
828        renderer.update_consts(&mut self.data.lights, lights);
829
830        // Update event lights
831        self.event_lights.retain_mut(|el| {
832            el.timeout -= dt;
833            el.timeout > 0.0
834        });
835
836        // Update shadow constants
837        let mut shadows = (
838            &scene_data.state.ecs().read_storage::<comp::Pos>(),
839            scene_data
840                .state
841                .ecs()
842                .read_storage::<crate::ecs::comp::Interpolated>()
843                .maybe(),
844            scene_data.state.ecs().read_storage::<comp::Scale>().maybe(),
845            &scene_data.state.ecs().read_storage::<comp::Body>(),
846            &scene_data.state.ecs().read_storage::<comp::Health>(),
847        )
848            .join()
849            .filter(|(_, _, _, _, health)| !health.is_dead)
850            .filter(|(pos, _, _, _, _)| {
851                pos.0.distance_squared(viewpoint_pos)
852                    < (loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powi(2)
853            })
854            .map(|(pos, interpolated, scale, _, _)| {
855                Shadow::new(
856                    // Use interpolated values pos if it is available
857                    interpolated.map_or(pos.0, |i| i.pos),
858                    scale.map_or(1.0, |s| s.0),
859                )
860            })
861            .collect::<Vec<_>>();
862        shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(viewpoint_pos) as i32);
863        shadows.truncate(MAX_SHADOW_COUNT);
864        renderer.update_consts(&mut self.data.shadows, &shadows);
865
866        // Remember to put the new loaded distance back in the scene.
867        self.loaded_distance = loaded_distance;
868
869        // Update light projection matrices for the shadow map.
870
871        // When the target time of day and time of day have a large discrepancy
872        // (i.e two days), the linear interpolation causes brght flashing effects
873        // in the sky. This will snap the time of day to the target time of day
874        // for the client to avoid the flashing effect if flashing lights is
875        // disabled.
876        const DAY: f64 = 60.0 * 60.0 * 24.0;
877        let time_of_day = scene_data.state.get_time_of_day();
878        let max_lerp_period = if scene_data.flashing_lights_enabled {
879            DAY * 2.0
880        } else {
881            DAY * 0.25
882        };
883        self.interpolated_time_of_day =
884            Some(self.interpolated_time_of_day.map_or(time_of_day, |tod| {
885                if (tod - time_of_day).abs() > max_lerp_period {
886                    time_of_day
887                } else {
888                    Lerp::lerp(tod, time_of_day, dt as f64)
889                }
890            }));
891        let time_of_day = self.interpolated_time_of_day.unwrap_or(time_of_day);
892        let focus_pos = self.camera.get_focus_pos();
893        let focus_off = focus_pos.map(|e| e.trunc());
894
895        // Update global constants.
896        renderer.update_consts(&mut self.data.globals, &[Globals::new(
897            view_mat,
898            proj_mat,
899            cam_pos,
900            focus_pos,
901            self.loaded_distance,
902            self.lod.get_data().tgt_detail as f32,
903            self.map_bounds,
904            time_of_day,
905            scene_data.state.get_time(),
906            self.local_time,
907            renderer.resolution().as_(),
908            Vec2::new(SHADOW_NEAR, SHADOW_FAR),
909            lights.len(),
910            shadows.len(),
911            NUM_DIRECTED_LIGHTS,
912            scene_data
913                .state
914                .terrain()
915                .get((cam_pos + focus_off).map(|e| e.floor() as i32))
916                .ok()
917                // Don't block the camera's view in solid blocks if the player is a moderator
918                .filter(|b| !(b.is_filled() && client.is_moderator()))
919                .map(|b| b.kind())
920                .unwrap_or(BlockKind::Air),
921            self.select_pos.map(|e| e - focus_off.map(|e| e as i32)),
922            scene_data.gamma,
923            scene_data.exposure,
924            self.last_lightning.unwrap_or((Vec3::zero(), -1000.0)),
925            self.wind_vel,
926            scene_data.ambiance,
927            self.camera.get_mode(),
928            scene_data.sprite_render_distance - 20.0,
929        )]);
930        renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv));
931        renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
932
933        // Maintain LoD.
934        self.lod.maintain(renderer, client, focus_pos, &self.camera);
935
936        // Maintain tethers.
937        self.tether_mgr.maintain(renderer, client, focus_pos);
938
939        // Maintain debug shapes
940        self.debug.maintain(renderer);
941
942        // Maintain the terrain.
943        let (
944            _visible_bounds,
945            visible_light_volume,
946            visible_psr_bounds,
947            visible_occlusion_volume,
948            visible_por_bounds,
949        ) = self.terrain.maintain(
950            renderer,
951            scene_data,
952            focus_pos,
953            self.loaded_distance,
954            &self.camera,
955        );
956
957        // Maintain the figures.
958        let _figure_bounds = self.figure_mgr.maintain(
959            renderer,
960            &mut self.trail_mgr,
961            scene_data,
962            visible_psr_bounds,
963            visible_por_bounds,
964            &self.camera,
965            Some(&self.terrain),
966        );
967
968        let fov = self.camera.get_effective_fov();
969        let aspect_ratio = self.camera.get_aspect_ratio();
970        let view_dir = ((focus_pos.map(f32::fract)) - cam_pos).normalized();
971
972        // We need to compute these offset matrices to transform world space coordinates
973        // to the translated ones we use when multiplying by the light space
974        // matrix; this helps avoid precision loss during the
975        // multiplication.
976        let look_at = cam_pos;
977        let new_dir = view_dir;
978        let new_dir = new_dir.normalized();
979        let up: math::Vec3<f32> = math::Vec3::unit_y();
980
981        // Optimal warping for directed lights:
982        //
983        // n_opt = 1 / sin y (z_n + √(z_n + (f - n) sin y))
984        //
985        // where n is near plane, f is far plane, y is the tilt angle between view and
986        // light direction, and n_opt is the optimal near plane.
987        // We also want a way to transform and scale this matrix (* 0.5 + 0.5) in order
988        // to transform it correctly into texture coordinates, as well as
989        // OpenGL coordinates.  Note that the matrix for directional light
990        // is *already* linear in the depth buffer.
991        //
992        // Also, observe that we flip the texture sampling matrix in order to account
993        // for the fact that DirectX renders top-down.
994        let texture_mat = Mat4::<f32>::scaling_3d::<Vec3<f32>>(Vec3::new(0.5, -0.5, 1.0))
995            * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0));
996
997        let directed_mats = |d_view_mat: math::Mat4<f32>,
998                             d_dir: math::Vec3<f32>,
999                             volume: &Vec<math::Vec3<f32>>|
1000         -> (Mat4<f32>, Mat4<f32>) {
1001            // NOTE: Light view space, right-handed.
1002            let v_p_orig = math::Vec3::from(d_view_mat * math::Vec4::from_direction(new_dir));
1003            let mut v_p = v_p_orig.normalized();
1004            let cos_gamma = new_dir.map(f64::from).dot(d_dir.map(f64::from));
1005            let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt();
1006            let gamma = sin_gamma.asin();
1007            let view_mat = math::Mat4::from_col_array(view_mat.into_col_array());
1008            // coordinates are transformed from world space (right-handed) to view space
1009            // (right-handed).
1010            let bounds1 = math::fit_psr(
1011                view_mat.map_cols(math::Vec4::from),
1012                volume.iter().copied(),
1013                math::Vec4::homogenized,
1014            );
1015            let n_e = f64::from(-bounds1.max.z);
1016            let factor = compute_warping_parameter_perspective(
1017                gamma,
1018                n_e,
1019                f64::from(fov),
1020                f64::from(aspect_ratio),
1021            );
1022
1023            v_p.z = 0.0;
1024            v_p.normalize();
1025            let l_r: math::Mat4<f32> = if factor > EPSILON_UPSILON {
1026                // NOTE: Our coordinates are now in left-handed space, but v_p isn't; however,
1027                // v_p has no z component, so we don't have to adjust it for left-handed
1028                // spaces.
1029                math::Mat4::look_at_lh(math::Vec3::zero(), math::Vec3::unit_z(), v_p)
1030            } else {
1031                math::Mat4::identity()
1032            };
1033            // Convert from right-handed to left-handed coordinates.
1034            let directed_proj_mat = math::Mat4::new(
1035                1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
1036            );
1037
1038            let light_all_mat = l_r * directed_proj_mat * d_view_mat;
1039            // coordinates are transformed from world space (right-handed) to rotated light
1040            // space (left-handed).
1041            let bounds0 = math::fit_psr(
1042                light_all_mat,
1043                volume.iter().copied(),
1044                math::Vec4::homogenized,
1045            );
1046            // Vague idea: project z_n from the camera view to the light view (where it's
1047            // tilted by γ).
1048            //
1049            // NOTE: To transform a normal by M, we multiply by the transpose of the inverse
1050            // of M. For the cases below, we are transforming by an
1051            // already-inverted matrix, so the transpose of its inverse is
1052            // just the transpose of the original matrix.
1053            let (z_0, z_1) = {
1054                let f_e = f64::from(-bounds1.min.z).max(n_e);
1055                // view space, right-handed coordinates.
1056                let p_z = bounds1.max.z;
1057                // rotated light space, left-handed coordinates.
1058                let p_y = bounds0.min.y;
1059                let p_x = bounds0.center().x;
1060                // moves from view-space (right-handed) to world space (right-handed)
1061                let view_inv = view_mat.inverted();
1062                // moves from rotated light space (left-handed) to world space (right-handed).
1063                let light_all_inv = light_all_mat.inverted();
1064
1065                // moves from view-space (right-handed) to world-space (right-handed).
1066                let view_point = view_inv
1067                    * math::Vec4::from_point(
1068                        -math::Vec3::unit_z() * p_z, /* + math::Vec4::unit_w() */
1069                    );
1070                let view_plane = view_mat.transposed() * -math::Vec4::unit_z();
1071
1072                // moves from rotated light space (left-handed) to world space (right-handed).
1073                let light_point = light_all_inv
1074                    * math::Vec4::from_point(
1075                        math::Vec3::unit_y() * p_y, /* + math::Vec4::unit_w() */
1076                    );
1077                let light_plane = light_all_mat.transposed() * math::Vec4::unit_y();
1078
1079                // moves from rotated light space (left-handed) to world space (right-handed).
1080                let shadow_point = light_all_inv
1081                    * math::Vec4::from_point(
1082                        math::Vec3::unit_x() * p_x, /* + math::Vec4::unit_w() */
1083                    );
1084                let shadow_plane = light_all_mat.transposed() * math::Vec4::unit_x();
1085
1086                // Find the point at the intersection of the three planes; note that since the
1087                // equations are already in right-handed world space, we don't need to negate
1088                // the z coordinates.
1089                let solve_p0 = math::Mat4::new(
1090                    view_plane.x,
1091                    view_plane.y,
1092                    view_plane.z,
1093                    0.0,
1094                    light_plane.x,
1095                    light_plane.y,
1096                    light_plane.z,
1097                    0.0,
1098                    shadow_plane.x,
1099                    shadow_plane.y,
1100                    shadow_plane.z,
1101                    0.0,
1102                    0.0,
1103                    0.0,
1104                    0.0,
1105                    1.0,
1106                );
1107
1108                // in world-space (right-handed).
1109                let plane_dist = math::Vec4::new(
1110                    view_plane.dot(view_point),
1111                    light_plane.dot(light_point),
1112                    shadow_plane.dot(shadow_point),
1113                    1.0,
1114                );
1115                let p0_world = solve_p0.inverted() * plane_dist;
1116                // in rotated light-space (left-handed).
1117                let p0 = light_all_mat * p0_world;
1118                let mut p1 = p0;
1119                // in rotated light-space (left-handed).
1120                p1.y = bounds0.max.y;
1121
1122                // transforms from rotated light-space (left-handed) to view space
1123                // (right-handed).
1124                let view_from_light_mat = view_mat * light_all_inv;
1125                // z0 and z1 are in view space (right-handed).
1126                let z0 = view_from_light_mat * p0;
1127                let z1 = view_from_light_mat * p1;
1128
1129                // Extract the homogenized forward component (right-handed).
1130                //
1131                // NOTE: I don't think the w component should be anything but 1 here, but
1132                // better safe than sorry.
1133                (
1134                    f64::from(z0.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
1135                    f64::from(z1.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
1136                )
1137            };
1138
1139            // all of this is in rotated light-space (left-handed).
1140            let mut light_focus_pos: math::Vec3<f32> = math::Vec3::zero();
1141            light_focus_pos.x = bounds0.center().x;
1142            light_focus_pos.y = bounds0.min.y;
1143            light_focus_pos.z = bounds0.center().z;
1144
1145            let d = f64::from(bounds0.max.y - bounds0.min.y).abs();
1146
1147            let w_l_y = d;
1148
1149            // NOTE: See section 5.1.2.2 of Lloyd's thesis.
1150            // NOTE: Since z_1 and z_0 are in the same coordinate space, we don't have to
1151            // worry about the handedness of their ratio.
1152            let alpha = z_1 / z_0;
1153            let alpha_sqrt = alpha.sqrt();
1154            let directed_near_normal = if factor < 0.0 {
1155                // Standard shadow map to LiSPSM
1156                (1.0 + alpha_sqrt - factor * (alpha - 1.0)) / ((alpha - 1.0) * (factor + 1.0))
1157            } else {
1158                // LiSPSM to PSM
1159                ((alpha_sqrt - 1.0) * (factor * alpha_sqrt + 1.0)).recip()
1160            };
1161
1162            // Equation 5.14 - 5.16
1163            let y_ = |v: f64| w_l_y * (v + directed_near_normal).abs();
1164            let directed_near = y_(0.0) as f32;
1165            let directed_far = y_(1.0) as f32;
1166            light_focus_pos.y = if factor > EPSILON_UPSILON {
1167                light_focus_pos.y - directed_near
1168            } else {
1169                light_focus_pos.y
1170            };
1171            // Left-handed translation.
1172            let w_v: math::Mat4<f32> = math::Mat4::translation_3d(-math::Vec3::new(
1173                light_focus_pos.x,
1174                light_focus_pos.y,
1175                light_focus_pos.z,
1176            ));
1177            let shadow_view_mat: math::Mat4<f32> = w_v * light_all_mat;
1178            let w_p: math::Mat4<f32> = {
1179                if factor > EPSILON_UPSILON {
1180                    // Projection for y
1181                    let near = directed_near;
1182                    let far = directed_far;
1183                    let left = -1.0;
1184                    let right = 1.0;
1185                    let bottom = -1.0;
1186                    let top = 1.0;
1187                    let s_x = 2.0 * near / (right - left);
1188                    let o_x = (right + left) / (right - left);
1189                    let s_z = 2.0 * near / (top - bottom);
1190                    let o_z = (top + bottom) / (top - bottom);
1191
1192                    let s_y = (far + near) / (far - near);
1193                    let o_y = -2.0 * far * near / (far - near);
1194
1195                    math::Mat4::new(
1196                        s_x, o_x, 0.0, 0.0, 0.0, s_y, 0.0, o_y, 0.0, o_z, s_z, 0.0, 0.0, 1.0, 0.0,
1197                        0.0,
1198                    )
1199                } else {
1200                    math::Mat4::identity()
1201                }
1202            };
1203
1204            let shadow_all_mat: math::Mat4<f32> = w_p * shadow_view_mat;
1205            // coordinates are transformed from world space (right-handed)
1206            // to post-warp light space (left-handed), then homogenized.
1207            let math::Aabb::<f32> {
1208                min:
1209                    math::Vec3 {
1210                        x: xmin,
1211                        y: ymin,
1212                        z: zmin,
1213                    },
1214                max:
1215                    math::Vec3 {
1216                        x: xmax,
1217                        y: ymax,
1218                        z: zmax,
1219                    },
1220            } = math::fit_psr(
1221                shadow_all_mat,
1222                volume.iter().copied(),
1223                math::Vec4::homogenized,
1224            );
1225            let s_x = 2.0 / (xmax - xmin);
1226            let s_y = 2.0 / (ymax - ymin);
1227            let s_z = 1.0 / (zmax - zmin);
1228            let o_x = -(xmax + xmin) / (xmax - xmin);
1229            let o_y = -(ymax + ymin) / (ymax - ymin);
1230            let o_z = -zmin / (zmax - zmin);
1231            let directed_proj_mat = Mat4::new(
1232                s_x, 0.0, 0.0, o_x, 0.0, s_y, 0.0, o_y, 0.0, 0.0, s_z, o_z, 0.0, 0.0, 0.0, 1.0,
1233            );
1234
1235            let shadow_all_mat: Mat4<f32> = Mat4::from_col_arrays(shadow_all_mat.into_col_arrays());
1236
1237            let directed_texture_proj_mat = texture_mat * directed_proj_mat;
1238            (
1239                directed_proj_mat * shadow_all_mat,
1240                directed_texture_proj_mat * shadow_all_mat,
1241            )
1242        };
1243
1244        let weather = client
1245            .state()
1246            .max_weather_near(focus_off.xy() + cam_pos.xy());
1247        self.wind_vel = weather.wind_vel();
1248        if weather.rain > RAIN_THRESHOLD {
1249            let weather = client.weather_at_player();
1250            let rain_vel = weather.rain_vel();
1251            let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);
1252
1253            self.integrated_rain_vel += rain_vel.magnitude() * dt;
1254            let rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), rain_vel);
1255
1256            let (shadow_mat, texture_mat) =
1257                directed_mats(rain_view_mat, rain_vel, &visible_occlusion_volume);
1258
1259            let rain_occlusion_locals = RainOcclusionLocals::new(
1260                shadow_mat,
1261                texture_mat,
1262                rain_dir_mat,
1263                weather.rain,
1264                self.integrated_rain_vel,
1265            );
1266
1267            renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
1268        } else if self.integrated_rain_vel > 0.0 {
1269            self.integrated_rain_vel = 0.0;
1270            // Need to set rain to zero
1271            let rain_occlusion_locals = RainOcclusionLocals::default();
1272            renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
1273        }
1274
1275        let sun_dir = scene_data.get_sun_dir();
1276        let is_daylight = sun_dir.z < 0.0;
1277        if renderer.pipeline_modes().shadow.is_map() && (is_daylight || !lights.is_empty()) {
1278            let (point_shadow_res, _directed_shadow_res) = renderer.get_shadow_resolution();
1279            // NOTE: The aspect ratio is currently always 1 for our cube maps, since they
1280            // are equal on all sides.
1281            let point_shadow_aspect = point_shadow_res.x as f32 / point_shadow_res.y as f32;
1282            // Construct matrices to transform from world space to light space for the sun
1283            // and moon.
1284            let directed_light_dir = sun_dir;
1285
1286            // We upload view matrices as well, to assist in linearizing vertex positions.
1287            // (only for directional lights, so far).
1288            let mut directed_shadow_mats = Vec::with_capacity(6);
1289
1290            let light_view_mat = math::Mat4::look_at_rh(look_at, look_at + directed_light_dir, up);
1291            let (shadow_mat, texture_mat) =
1292                directed_mats(light_view_mat, directed_light_dir, &visible_light_volume);
1293
1294            let shadow_locals = ShadowLocals::new(shadow_mat, texture_mat);
1295
1296            renderer.update_consts(&mut self.data.shadow_mats, &[shadow_locals]);
1297
1298            directed_shadow_mats.push(light_view_mat);
1299            // This leaves us with five dummy slots, which we push as defaults.
1300            directed_shadow_mats
1301                .extend_from_slice(&[math::Mat4::default(); 6 - NUM_DIRECTED_LIGHTS] as _);
1302            // Now, construct the full projection matrices in the first two directed light
1303            // slots.
1304            let mut shadow_mats = Vec::with_capacity(6 * (lights.len() + 1));
1305            shadow_mats.resize_with(6, PointLightMatrix::default);
1306            // Now, we tackle point lights.
1307            // First, create a perspective projection matrix at 90 degrees (to cover a whole
1308            // face of the cube map we're using); we use a negative near plane to exactly
1309            // match OpenGL's behavior if we use a left-handed coordinate system everywhere
1310            // else.
1311            let shadow_proj = camera::perspective_rh_zo_general(
1312                90.0f32.to_radians(),
1313                point_shadow_aspect,
1314                1.0 / SHADOW_NEAR,
1315                1.0 / SHADOW_FAR,
1316            );
1317            // NOTE: We negate here to emulate a right-handed projection with a negative
1318            // near plane, which produces the correct transformation to exactly match
1319            // OpenGL's rendering behavior if we use a left-handed coordinate
1320            // system everywhere else.
1321            let shadow_proj = shadow_proj * Mat4::scaling_3d(-1.0);
1322
1323            // Next, construct the 6 orientations we'll use for the six faces, in terms of
1324            // their (forward, up) vectors.
1325            let orientations = [
1326                (Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
1327                (Vec3::new(-1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
1328                (Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 0.0, 1.0)),
1329                (Vec3::new(0.0, -1.0, 0.0), Vec3::new(0.0, 0.0, -1.0)),
1330                (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.0, -1.0, 0.0)),
1331                (Vec3::new(0.0, 0.0, -1.0), Vec3::new(0.0, -1.0, 0.0)),
1332            ];
1333
1334            // NOTE: We could create the shadow map collection at the same time as the
1335            // lights, but then we'd have to sort them both, which wastes time.  Plus, we
1336            // want to prepend our directed lights.
1337            shadow_mats.extend(lights.iter().flat_map(|light| {
1338                // Now, construct the full projection matrix by making the light look at each
1339                // cube face.
1340                let eye = Vec3::new(light.pos[0], light.pos[1], light.pos[2]) - focus_off;
1341                orientations.iter().map(move |&(forward, up)| {
1342                    // NOTE: We don't currently try to linearize point lights or need a separate
1343                    // transform for them.
1344                    PointLightMatrix::new(shadow_proj * Mat4::look_at_lh(eye, eye + forward, up))
1345                })
1346            }));
1347
1348            for (i, val) in shadow_mats.into_iter().enumerate() {
1349                self.data.point_light_matrices[i] = val
1350            }
1351        }
1352
1353        // Remove unused figures.
1354        self.figure_mgr.clean(scene_data.tick);
1355
1356        // Maintain audio
1357        self.sfx_mgr.maintain(
1358            audio,
1359            scene_data.state,
1360            scene_data.viewpoint_entity,
1361            &self.camera,
1362            &self.terrain,
1363            client,
1364        );
1365
1366        self.ambience_mgr.maintain(
1367            audio,
1368            &settings.audio,
1369            scene_data.state,
1370            client,
1371            &self.camera,
1372        );
1373
1374        self.music_mgr.maintain(audio, scene_data.state, client);
1375    }
1376
1377    pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
1378
1379    /// Render the scene using the provided `Drawer`.
1380    pub fn render(
1381        &self,
1382        drawer: &mut Drawer<'_>,
1383        state: &State,
1384        viewpoint_entity: EcsEntity,
1385        tick: u64,
1386        scene_data: &SceneData,
1387    ) {
1388        span!(_guard, "render", "Scene::render");
1389        let sun_dir = scene_data.get_sun_dir();
1390        let is_daylight = sun_dir.z < 0.0;
1391        let focus_pos = self.camera.get_focus_pos();
1392        let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
1393        let is_rain = state.max_weather_near(cam_pos.xy()).rain > RAIN_THRESHOLD;
1394        let culling_mode = if scene_data
1395            .state
1396            .terrain()
1397            .get_key(scene_data.state.terrain().pos_key(cam_pos.as_()))
1398            .is_some_and(|c| cam_pos.z < c.meta().alt() - terrain::UNDERGROUND_ALT)
1399        {
1400            CullingMode::Underground
1401        } else {
1402            CullingMode::Surface
1403        };
1404
1405        let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
1406
1407        // would instead have this as an extension.
1408        if drawer.pipeline_modes().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) {
1409            if is_daylight {
1410                prof_span!("directed shadows");
1411                if let Some(mut shadow_pass) = drawer.shadow_pass() {
1412                    // Render terrain directed shadows.
1413                    self.terrain.render_shadows(
1414                        &mut shadow_pass.draw_terrain_shadows(),
1415                        focus_pos,
1416                        culling_mode,
1417                    );
1418
1419                    // Render figure directed shadows.
1420                    self.figure_mgr.render_shadows(
1421                        &mut shadow_pass.draw_figure_shadows(),
1422                        state,
1423                        tick,
1424                        camera_data,
1425                    );
1426                    self.debug
1427                        .render_shadows(&mut shadow_pass.draw_debug_shadows());
1428                }
1429            }
1430
1431            // Render terrain point light shadows.
1432            {
1433                prof_span!("point shadows");
1434                drawer.draw_point_shadows(
1435                    &self.data.point_light_matrices,
1436                    self.terrain.chunks_for_point_shadows(focus_pos),
1437                )
1438            }
1439        }
1440        // Render rain occlusion texture
1441        if is_rain {
1442            prof_span!("rain occlusion");
1443            if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
1444                self.terrain
1445                    .render_rain_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
1446
1447                self.figure_mgr.render_rain_occlusion(
1448                    &mut occlusion_pass.draw_figure_shadows(),
1449                    state,
1450                    tick,
1451                    camera_data,
1452                );
1453            }
1454        }
1455
1456        prof_span!(guard, "main pass");
1457        if let Some(mut first_pass) = drawer.first_pass() {
1458            self.figure_mgr.render_viewpoint(
1459                &mut first_pass.draw_figures(),
1460                state,
1461                viewpoint_entity,
1462                tick,
1463                camera_data,
1464            );
1465
1466            self.terrain
1467                .render(&mut first_pass, focus_pos, culling_mode);
1468
1469            self.figure_mgr.render(
1470                &mut first_pass.draw_figures(),
1471                state,
1472                viewpoint_entity,
1473                tick,
1474                camera_data,
1475            );
1476
1477            self.lod.render(&mut first_pass, culling_mode);
1478
1479            // Render the skybox.
1480            first_pass.draw_skybox(&self.skybox.model);
1481
1482            // Draws sprites
1483            let mut sprite_drawer = first_pass.draw_sprites(
1484                &self.terrain.sprite_globals,
1485                &self.terrain.sprite_render_state.sprite_atlas_textures,
1486            );
1487            self.figure_mgr.render_sprites(
1488                &mut sprite_drawer,
1489                state,
1490                cam_pos,
1491                scene_data.sprite_render_distance,
1492            );
1493            self.terrain.render_sprites(
1494                &mut sprite_drawer,
1495                focus_pos,
1496                cam_pos,
1497                scene_data.sprite_render_distance,
1498                culling_mode,
1499            );
1500            drop(sprite_drawer);
1501
1502            // Render tethers.
1503            self.tether_mgr.render(&mut first_pass);
1504
1505            // Draws translucent
1506            self.terrain.render_translucent(&mut first_pass, focus_pos);
1507
1508            // Render particle effects.
1509            self.particle_mgr
1510                .render(&mut first_pass.draw_particles(), scene_data);
1511
1512            // Render debug shapes
1513            self.debug.render(&mut first_pass.draw_debug());
1514        }
1515        drop(guard);
1516    }
1517
1518    pub fn maintain_debug_hitboxes(
1519        &mut self,
1520        client: &Client,
1521        settings: &Settings,
1522        hitboxes: &mut HashMap<specs::Entity, DebugShapeId>,
1523        tracks: &mut HashMap<Vec2<i32>, Vec<DebugShapeId>>,
1524        gizmos: &mut Vec<(DebugShapeId, common::resources::Time, bool)>,
1525    ) {
1526        let ecs = client.state().ecs();
1527        {
1528            let mut current_chunks = hashbrown::HashSet::new();
1529            let terrain_grid = ecs.read_resource::<TerrainGrid>();
1530            for (key, chunk) in terrain_grid.iter() {
1531                current_chunks.insert(key);
1532                tracks.entry(key).or_insert_with(|| {
1533                    let mut ret = Vec::new();
1534                    for bezier in chunk.meta().tracks().iter() {
1535                        let shape_id = self.debug.add_shape(DebugShape::TrainTrack {
1536                            path: *bezier,
1537                            rail_width: 0.25,
1538                            rail_sep: 1.0,
1539                            plank_width: 0.5,
1540                            plank_height: 0.125,
1541                            plank_sep: 2.0,
1542                        });
1543                        ret.push(shape_id);
1544                        self.debug
1545                            .set_context(shape_id, [0.0; 4], [1.0; 4], [0.0, 0.0, 0.0, 1.0]);
1546                    }
1547                    for point in chunk.meta().debug_points().iter() {
1548                        let shape_id = self.debug.add_shape(DebugShape::Cylinder {
1549                            radius: 0.1,
1550                            height: 0.1,
1551                        });
1552                        ret.push(shape_id);
1553                        self.debug.set_context(
1554                            shape_id,
1555                            point.with_w(0.0).into_array(),
1556                            [1.0; 4],
1557                            [0.0, 0.0, 0.0, 1.0],
1558                        );
1559                    }
1560                    for line in chunk.meta().debug_lines().iter() {
1561                        let shape_id = self
1562                            .debug
1563                            .add_shape(DebugShape::Line([line.start, line.end], 0.1));
1564                        ret.push(shape_id);
1565                        self.debug
1566                            .set_context(shape_id, [0.0; 4], [1.0; 4], [0.0, 0.0, 0.0, 1.0]);
1567                    }
1568                    ret
1569                });
1570            }
1571            tracks.retain(|k, v| {
1572                let keep = current_chunks.contains(k);
1573                if !keep {
1574                    for shape in v.iter() {
1575                        self.debug.remove_shape(*shape);
1576                    }
1577                }
1578                keep
1579            });
1580        }
1581        let mut current_entities = hashbrown::HashSet::new();
1582        if settings.interface.toggle_hitboxes {
1583            let positions = ecs.read_component::<comp::Pos>();
1584            let colliders = ecs.read_component::<comp::Collider>();
1585            let orientations = ecs.read_component::<comp::Ori>();
1586            let scales = ecs.read_component::<comp::Scale>();
1587            let groups = ecs.read_component::<comp::Group>();
1588            for (entity, pos, collider, ori, scale, group) in (
1589                &ecs.entities(),
1590                &positions,
1591                &colliders,
1592                &orientations,
1593                scales.maybe(),
1594                groups.maybe(),
1595            )
1596                .join()
1597            {
1598                match collider {
1599                    comp::Collider::CapsulePrism {
1600                        p0,
1601                        p1,
1602                        radius,
1603                        z_min,
1604                        z_max,
1605                    } => {
1606                        let scale = scale.map_or(1.0, |s| s.0);
1607                        current_entities.insert(entity);
1608
1609                        let shape = DebugShape::CapsulePrism {
1610                            p0: *p0 * scale,
1611                            p1: *p1 * scale,
1612                            radius: *radius * scale,
1613                            height: (*z_max - *z_min) * scale,
1614                        };
1615
1616                        // If this shape no longer matches, remove the old one
1617                        if let Some(shape_id) = hitboxes.get(&entity) {
1618                            if self.debug.get_shape(*shape_id).is_some_and(|s| s != &shape) {
1619                                self.debug.remove_shape(*shape_id);
1620                                hitboxes.remove(&entity);
1621                            }
1622                        }
1623
1624                        let shape_id = hitboxes
1625                            .entry(entity)
1626                            .or_insert_with(|| self.debug.add_shape(shape));
1627                        let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min * scale, 0.0];
1628                        let color = if group == Some(&comp::group::ENEMY) {
1629                            [1.0, 0.0, 0.0, 0.5]
1630                        } else if group == Some(&comp::group::NPC) {
1631                            [0.0, 0.0, 1.0, 0.5]
1632                        } else {
1633                            [0.0, 1.0, 0.0, 0.5]
1634                        };
1635                        //let color = [1.0, 1.0, 1.0, 1.0];
1636                        let ori = ori.to_quat();
1637                        let hb_ori = [ori.x, ori.y, ori.z, ori.w];
1638                        self.debug.set_context(*shape_id, hb_pos, color, hb_ori);
1639                    },
1640                    comp::Collider::Voxel { .. }
1641                    | comp::Collider::Volume(_)
1642                    | comp::Collider::Point => {
1643                        // ignore terrain-like or point-hitboxes
1644                    },
1645                }
1646            }
1647        }
1648        hitboxes.retain(|k, v| {
1649            let keep = current_entities.contains(k);
1650            if !keep {
1651                self.debug.remove_shape(*v);
1652            }
1653            keep
1654        });
1655
1656        let time = client.state().get_time();
1657        gizmos.retain(|(id, end_time, _)| {
1658            let keep = end_time.0 > time;
1659            if !keep {
1660                self.debug.remove_shape(*id);
1661            }
1662            keep
1663        });
1664    }
1665
1666    pub fn maintain_debug_vectors(&mut self, client: &Client, lines: &mut PlayerDebugLines) {
1667        lines
1668            .chunk_normal
1669            .take()
1670            .map(|id| self.debug.remove_shape(id));
1671        lines.fluid_vel.take().map(|id| self.debug.remove_shape(id));
1672        lines.wind.take().map(|id| self.debug.remove_shape(id));
1673        lines.vel.take().map(|id| self.debug.remove_shape(id));
1674        if self.debug_vectors_enabled {
1675            let ecs = client.state().ecs();
1676
1677            let vels = &ecs.read_component::<comp::Vel>();
1678            let Some(vel) = vels.get(client.entity()) else {
1679                return;
1680            };
1681
1682            let phys_states = &ecs.read_component::<comp::PhysicsState>();
1683            let Some(phys) = phys_states.get(client.entity()) else {
1684                return;
1685            };
1686
1687            let positions = &ecs.read_component::<comp::Pos>();
1688            let Some(pos) = positions.get(client.entity()) else {
1689                return;
1690            };
1691
1692            let weather = ecs.read_resource::<WeatherGrid>();
1693            // take id and remove to delete the previous lines.
1694
1695            const LINE_WIDTH: f32 = 0.05;
1696            // Fluid Velocity
1697            {
1698                let Some(fluid) = phys.in_fluid else {
1699                    return;
1700                };
1701                let shape = DebugShape::Line([pos.0, pos.0 + fluid.flow_vel().0 / 2.], LINE_WIDTH);
1702                let id = self.debug.add_shape(shape);
1703                lines.fluid_vel = Some(id);
1704                self.debug
1705                    .set_context(id, [0.0; 4], [0.18, 0.72, 0.87, 0.8], [0.0, 0.0, 0.0, 1.0]);
1706            }
1707            // Chunk Terrain Normal Vector
1708            {
1709                let Some(chunk) = client.current_chunk() else {
1710                    return;
1711                };
1712                let shape = DebugShape::Line(
1713                    [
1714                        pos.0,
1715                        pos.0
1716                            + chunk
1717                                .meta()
1718                                .approx_chunk_terrain_normal()
1719                                .unwrap_or(Vec3::unit_z())
1720                                * 2.5,
1721                    ],
1722                    LINE_WIDTH,
1723                );
1724                let id = self.debug.add_shape(shape);
1725                lines.chunk_normal = Some(id);
1726                self.debug
1727                    .set_context(id, [0.0; 4], [0.22, 0.63, 0.1, 0.8], [0.0, 0.0, 0.0, 1.0]);
1728            }
1729            // Wind
1730            {
1731                let wind = weather.get_interpolated(pos.0.xy()).wind_vel();
1732                let shape = DebugShape::Line([pos.0, pos.0 + wind * 5.0], LINE_WIDTH);
1733                let id = self.debug.add_shape(shape);
1734                lines.wind = Some(id);
1735                self.debug
1736                    .set_context(id, [0.0; 4], [0.76, 0.76, 0.76, 0.8], [0.0, 0.0, 0.0, 1.0]);
1737            }
1738            // Player Vel
1739            {
1740                let shape = DebugShape::Line([pos.0, pos.0 + vel.0 / 2.0], LINE_WIDTH);
1741                let id = self.debug.add_shape(shape);
1742                lines.vel = Some(id);
1743                self.debug
1744                    .set_context(id, [0.0; 4], [0.98, 0.76, 0.01, 0.8], [0.0, 0.0, 0.0, 1.0]);
1745            }
1746        }
1747    }
1748}