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