Skip to main content

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