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
66const CURSOR_PAN_SCALE: f32 = 0.005;
68
69pub(crate) const MAX_LIGHT_COUNT: usize = 20; pub(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; const SHADOW_DIST_RADIUS: f32 = 8.0;
76const SHADOW_MAX_DIST: f32 = 96.0; const EPSILON_UPSILON: f64 = -1.0;
79
80const SHADOW_NEAR: f32 = 0.25; const SHADOW_FAR: f32 = 128.0; const RUNNING_THRESHOLD: f32 = 0.7;
86
87const RAIN_THRESHOLD: f32 = 0.0;
89
90pub 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 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, 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 weapon_trails_enabled: bool,
159 pub flashing_lights_enabled: bool,
160 pub figure_lod_render_distance: f32,
161 pub is_aiming: bool,
162 pub interpolated_time_of_day: Option<f64>,
163 pub wind_vel: Vec2<f32>,
164}
165
166impl SceneData<'_> {
167 pub fn get_sun_dir(&self) -> Vec3<f32> {
168 TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_sun_dir()
169 }
170
171 pub fn get_moon_dir(&self) -> Vec3<f32> {
172 TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_moon_dir()
173 }
174}
175
176fn compute_scalar_fov<F: Float>(_near_plane: F, fov: F, aspect: F) -> F {
217 let two = F::one() + F::one();
218 let theta_y = fov / two;
219 let theta_x = (aspect * theta_y.tan()).atan();
220 theta_x.min(theta_y)
221}
222
223fn compute_warping_parameter<F: Float + FloatConst>(
255 gamma: F,
256 (gamma_a, gamma_b, gamma_c): (F, F, F),
257 (eta_b, eta_c): (F, F),
258) -> F {
259 if gamma < gamma_a {
260 -F::one()
261 } else if gamma_a <= gamma && gamma < gamma_b {
263 -F::one() + (eta_b + F::one()) * (F::one() - (F::FRAC_PI_2() * (gamma - gamma_a) / (gamma_b - gamma_a)).cos())
265 } else if gamma_b <= gamma && gamma < gamma_c {
267 eta_b + (eta_c - eta_b) * (F::FRAC_PI_2() * (gamma - gamma_b) / (gamma_c - gamma_b)).sin()
268 } else {
269 eta_c
270 }
271 .max(-F::one()).min(F::one())
273}
274
275fn compute_warping_parameter_perspective<F: Float + FloatConst>(
290 gamma: F,
291 near_plane: F,
292 fov: F,
293 aspect: F,
294) -> F {
295 let theta = compute_scalar_fov(near_plane, fov, aspect);
296 let two = F::one() + F::one();
297 let three = two + F::one();
298 let ten = three + three + three + F::one();
299 compute_warping_parameter(
300 gamma,
301 (
302 theta / three,
303 theta,
304 theta + (three / ten) * (F::FRAC_PI_2() - theta),
305 ),
306 (-two / ten, F::zero()),
307 )
308}
309
310impl Scene {
311 pub fn new(
313 renderer: &mut Renderer,
314 lazy_init: &mut SpriteRenderContextLazy,
315 client: &Client,
316 settings: &Settings,
317 ) -> Self {
318 let resolution = renderer.resolution().map(|e| e as f32);
319 let sprite_render_context = lazy_init(renderer);
320
321 let data = GlobalModel {
322 globals: renderer.create_consts(&[Globals::default()]),
323 lights: renderer.create_consts(&[Light::default(); MAX_LIGHT_COUNT]),
324 shadows: renderer.create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]),
325 shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]),
326 rain_occlusion_mats: renderer
327 .create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]),
328 point_light_matrices: Box::new(
329 [PointLightMatrix::default(); MAX_POINT_LIGHT_MATRICES_COUNT],
330 ),
331 };
332
333 let lod = Lod::new(renderer, client, settings);
334
335 let globals_bind_group = renderer.bind_globals(&data, lod.get_data());
336
337 let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
338
339 let camera_mode = match client.presence() {
340 Some(comp::PresenceKind::Spectator) => CameraMode::Freefly,
341 _ => CameraMode::ThirdPerson,
342 };
343
344 let calendar = client.state().ecs().read_resource::<Calendar>();
345
346 Self {
347 data,
348 globals_bind_group,
349 camera: Camera::new(resolution.x / resolution.y, camera_mode),
350 camera_input_state: Vec2::zero(),
351 event_lights: Vec::new(),
352
353 skybox: Skybox {
354 model: renderer.create_model(&create_skybox_mesh()).unwrap(),
355 },
356 terrain,
357 debug: Debug::new(),
358 lod,
359 loaded_distance: 0.0,
360 map_bounds: Vec2::new(
361 client.world_data().min_chunk_alt(),
362 client.world_data().max_chunk_alt(),
363 ),
364 select_pos: None,
365 light_data: Vec::new(),
366 particle_mgr: ParticleMgr::new(renderer),
367 trail_mgr: TrailMgr::default(),
368 figure_mgr: FigureMgr::new(renderer),
369 tether_mgr: TetherMgr::new(renderer),
370 sfx_mgr: SfxMgr::default(),
371 music_mgr: MusicMgr::new(&calendar),
372 ambience_mgr: AmbienceMgr::new(ambience::load_ambience_items()),
373 integrated_rain_vel: 0.0,
374 wind_vel: Vec2::zero(),
375 interpolated_time_of_day: None,
376 last_lightning: None,
377 local_time: 0.0,
378 screen_fade: -0.5,
380 screen_fade_tgt: 1.0,
381 debug_vectors_enabled: false,
382 }
383 }
384
385 pub fn globals(&self) -> &Consts<Globals> { &self.data.globals }
387
388 pub fn camera(&self) -> &Camera { &self.camera }
390
391 pub fn terrain(&self) -> &Terrain<TerrainChunk> { &self.terrain }
393
394 pub fn lights(&self) -> &Vec<Light> { &self.light_data }
396
397 pub fn particle_mgr(&self) -> &ParticleMgr { &self.particle_mgr }
399
400 pub fn trail_mgr(&self) -> &TrailMgr { &self.trail_mgr }
402
403 pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr }
405
406 pub fn music_mgr(&self) -> &MusicMgr { &self.music_mgr }
407
408 pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera }
410
411 pub fn set_select_pos(&mut self, pos: Option<Vec3<i32>>) { self.select_pos = pos; }
413
414 pub fn select_pos(&self) -> Option<Vec3<i32>> { self.select_pos }
415
416 pub fn handle_input_event(&mut self, event: Event, client: &Client) -> bool {
421 match event {
422 Event::Resize(dims) => {
424 self.camera.set_aspect_ratio(dims.x as f32 / dims.y as f32);
425 true
426 },
427 Event::CursorPan(delta) => {
429 self.camera.rotate_by(Vec3::from(delta) * CURSOR_PAN_SCALE);
430 true
431 },
432 Event::Zoom(delta) => {
434 let cap = if client.is_moderator() {
435 ZOOM_CAP_ADMIN
436 } else {
437 ZOOM_CAP_PLAYER
438 };
439 let player_scale = client
443 .state()
444 .read_component_copied::<comp::Scale>(client.entity())
445 .map_or(1.0, |s| s.0);
446 if delta < 0.0 {
447 self.camera.zoom_switch(
448 delta * (0.05 + self.camera.get_distance() * 0.01) / (1.0 - delta * 0.01),
450 cap,
451 player_scale,
452 );
453 } else {
454 self.camera.zoom_switch(
455 delta * (0.05 + self.camera.get_distance() * 0.01),
456 cap,
457 player_scale,
458 );
459 }
460 true
461 },
462 Event::AnalogGameInput(input) => match input {
463 AnalogGameInput::CameraX(d) => {
464 self.camera_input_state.x = d;
465 true
466 },
467 AnalogGameInput::CameraY(d) => {
468 self.camera_input_state.y = d;
469 true
470 },
471 _ => false,
472 },
473 _ => false,
475 }
476 }
477
478 pub fn handle_outcome(
479 &mut self,
480 outcome: &Outcome,
481 scene_data: &SceneData,
482 audio: &mut AudioFrontend,
483 ) {
484 span!(_guard, "handle_outcome", "Scene::handle_outcome");
485 self.particle_mgr
486 .handle_outcome(outcome, scene_data, &self.figure_mgr);
487 self.sfx_mgr
488 .handle_outcome(outcome, audio, scene_data.client);
489
490 match outcome {
491 Outcome::Lightning { pos } => {
492 self.last_lightning = Some((*pos, scene_data.state.get_time()));
493 },
494 Outcome::Explosion {
495 pos,
496 power,
497 is_attack,
498 reagent,
499 ..
500 } => match reagent {
501 Some(Reagent::Earth) => {},
502 _ => self.event_lights.push(EventLight {
503 light: Light::new(
504 *pos,
505 match reagent {
506 Some(Reagent::Blue) => Rgb::new(0.15, 0.4, 1.0),
507 Some(Reagent::Green) => Rgb::new(0.0, 1.0, 0.0),
508 Some(Reagent::Purple) => Rgb::new(0.7, 0.0, 1.0),
509 Some(Reagent::Red) => {
510 if *is_attack {
511 Rgb::new(1.0, 0.5, 0.0)
512 } else {
513 Rgb::new(1.0, 0.0, 0.0)
514 }
515 },
516 Some(Reagent::White) => Rgb::new(1.0, 1.0, 1.0),
517 Some(Reagent::Yellow) => Rgb::new(1.0, 1.0, 0.0),
518 Some(Reagent::FireRain) => Rgb::new(1.0, 0.8, 0.3),
519 Some(Reagent::FireGigas) => Rgb::new(1.0, 0.6, 0.2),
520 Some(Reagent::Earth) | None => Rgb::new(1.0, 0.5, 0.0),
521 },
522 power
523 * if *is_attack || reagent.is_none() {
524 25.0
525 } else {
526 100.0
527 },
528 ),
529 timeout: match reagent {
530 Some(_) => 0.8,
531 None => 0.25,
532 },
533 fadeout: |timeout| timeout * 2.0,
534 }),
535 },
536 Outcome::ProjectileShot { .. } => {},
537 _ => {},
538 }
539 }
540
541 pub fn maintain(
544 &mut self,
545 renderer: &mut Renderer,
546 audio: &mut AudioFrontend,
547 scene_data: &SceneData,
548 client: &Client,
549 settings: &Settings,
550 mmap_face_north: bool,
551 ) {
552 span!(_guard, "maintain", "Scene::maintain");
553 let ecs = scene_data.state.ecs();
555
556 let dt = ecs.fetch::<DeltaTime>().0;
557
558 self.local_time += dt as f64 * ecs.fetch::<TimeScale>().0;
559
560 let positions = ecs.read_storage::<comp::Pos>();
561
562 let viewpoint_ori = ecs
563 .read_storage::<comp::Ori>()
564 .get(scene_data.viewpoint_entity)
565 .map_or(Quaternion::identity(), |ori| ori.to_quat());
566
567 let viewpoint_look_ori = ecs
568 .read_storage::<comp::CharacterActivity>()
569 .get(scene_data.viewpoint_entity)
570 .and_then(|activity| activity.look_dir)
571 .map(|dir| {
572 let d = dir.to_vec();
573
574 let pitch = (-d.z).asin();
575 let yaw = d.x.atan2(d.y);
576
577 Vec3::new(yaw, pitch, 0.0)
578 })
579 .unwrap_or_else(|| {
580 let q = viewpoint_ori;
581 let sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z);
582 let cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
583 let pitch = sinr_cosp.atan2(cosr_cosp);
584
585 let siny_cosp = 2.0 * (q.w * q.z + q.x * q.y);
586 let cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
587 let yaw = siny_cosp.atan2(cosy_cosp);
588
589 Vec3::new(-yaw, -pitch, 0.0)
590 });
591
592 let viewpoint_scale = ecs
593 .read_storage::<comp::Scale>()
594 .get(scene_data.viewpoint_entity)
595 .map_or(1.0, |scale| scale.0);
596
597 let (is_humanoid, viewpoint_height, viewpoint_eye_height) = ecs
598 .read_storage::<comp::Body>()
599 .get(scene_data.viewpoint_entity)
600 .map_or((false, 1.0, 0.0), |b| {
601 (
602 matches!(b, comp::Body::Humanoid(_)),
603 b.height() * viewpoint_scale,
604 b.eye_height(1.0) * viewpoint_scale, )
606 });
607 let viewpoint_eye_height = if matches!(self.camera.get_mode(), CameraMode::FirstPerson)
609 && let Some(char_state) = self
610 .figure_mgr
611 .states
612 .character_states
613 .get(&scene_data.viewpoint_entity)
614 && let Some(interpolated) = ecs
615 .read_storage::<Interpolated>()
616 .get(scene_data.viewpoint_entity)
617 {
618 char_state
620 .wpos_of(
621 char_state
622 .computed_skeleton
623 .head
624 .mul_point(Vec3::unit_z() * 0.6),
625 )
626 .z
627 - interpolated.pos.z
628 } else {
629 match ecs
632 .read_storage::<CharacterState>()
633 .get(scene_data.viewpoint_entity)
634 {
635 Some(CharacterState::Crawl) => viewpoint_eye_height * 0.3,
636 Some(CharacterState::Sit) => viewpoint_eye_height * 0.7,
637 Some(c) if c.is_stealthy() => viewpoint_eye_height * 0.6,
638 _ => viewpoint_eye_height,
639 }
640 };
641
642 if scene_data.mutable_viewpoint || matches!(self.camera.get_mode(), CameraMode::Freefly) {
643 self.camera.rotate_by(self.camera_input_state.with_z(0.0));
645 } else {
646 self.camera.set_orientation(viewpoint_look_ori);
648 }
649
650 let viewpoint_offset = if is_humanoid {
651 let is_running = ecs
652 .read_storage::<comp::Vel>()
653 .get(scene_data.viewpoint_entity)
654 .zip(
655 ecs.read_storage::<comp::PhysicsState>()
656 .get(scene_data.viewpoint_entity),
657 )
658 .map(|(v, ps)| {
659 (v.0 - ps.ground_vel).magnitude_squared() > RUNNING_THRESHOLD.powi(2)
660 })
661 .unwrap_or(false);
662
663 let on_ground = ecs
664 .read_storage::<comp::PhysicsState>()
665 .get(scene_data.viewpoint_entity)
666 .map(|p| p.on_ground.is_some());
667
668 let holding_ranged = client
669 .inventories()
670 .get(scene_data.viewpoint_entity)
671 .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
672 .and_then(|item| item.tool_info())
673 .is_some_and(|tool_kind| {
674 matches!(
675 tool_kind,
676 ToolKind::Bow | ToolKind::Staff | ToolKind::Sceptre | ToolKind::Throwable
677 )
678 })
679 || client
680 .current::<CharacterState>()
681 .is_some_and(|char_state| matches!(char_state, CharacterState::Throw(_)));
682
683 let up = match self.camera.get_mode() {
684 CameraMode::FirstPerson => {
685 if is_running && on_ground.unwrap_or(false) {
686 viewpoint_eye_height
687 + (scene_data.state.get_time() as f32 * 17.0).sin() * 0.05
688 } else {
689 viewpoint_eye_height
690 }
691 },
692 CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => {
693 viewpoint_height * 1.05 + settings.gameplay.aim_offset_y
694 },
695 CameraMode::ThirdPerson if scene_data.is_aiming => viewpoint_height * 1.05,
696 CameraMode::ThirdPerson => viewpoint_eye_height,
697 CameraMode::Freefly => 0.0,
698 };
699
700 let right = match self.camera.get_mode() {
701 CameraMode::FirstPerson => 0.0,
702 CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => {
703 settings.gameplay.aim_offset_x
704 },
705 CameraMode::ThirdPerson => 0.0,
706 CameraMode::Freefly => 0.0,
707 };
708
709 let tilt = self.camera.get_orientation().y;
711 let dist = self.camera.get_distance();
712
713 Vec3::unit_z() * (up - tilt.min(0.0).sin() * dist * 0.6)
714 + self.camera.right() * (right * viewpoint_scale)
715 } else {
716 self.figure_mgr
717 .viewpoint_offset(scene_data, scene_data.viewpoint_entity)
718 };
719
720 let entity_pos = positions
721 .get(scene_data.viewpoint_entity)
722 .map_or(Vec3::zero(), |pos| pos.0);
723
724 let viewpoint_pos = match self.camera.get_mode() {
725 CameraMode::FirstPerson => {
726 let viewpoint_pos = ecs
731 .read_storage::<Interpolated>()
732 .get(scene_data.viewpoint_entity)
733 .map_or(entity_pos, |i| i.pos.xy().with_z(entity_pos.z));
734 self.camera
735 .force_xy_focus_pos(viewpoint_pos + viewpoint_offset);
736 viewpoint_pos
737 },
738 CameraMode::ThirdPerson => {
739 let viewpoint_pos = entity_pos;
740 if let Some(health) = ecs
741 .read_storage::<comp::Health>()
742 .get(scene_data.viewpoint_entity)
743 && health.is_dead
744 {
745 self.camera.reset_focus();
747 } else {
748 self.screen_fade_tgt = 1.0;
749 self.camera.set_focus_pos(viewpoint_pos + viewpoint_offset)
750 };
751 viewpoint_pos
752 },
753 CameraMode::Freefly => entity_pos,
754 };
755
756 self.camera
758 .update(scene_data.state.get_time(), dt, scene_data.mouse_smoothing);
759
760 self.camera.compute_dependents(&scene_data.state.terrain());
762 let camera::Dependents {
763 view_mat,
764 view_mat_inv,
765 proj_mat,
766 proj_mat_inv,
767 cam_pos,
768 ..
769 } = self.camera.dependents();
770
771 let loaded_distance =
773 (0.98 * self.loaded_distance + 0.02 * scene_data.loaded_distance).max(0.01);
774
775 let lights = &mut self.light_data;
777 lights.clear();
778
779 self.particle_mgr.maintain(
781 renderer,
782 scene_data,
783 &self.terrain,
784 &self.figure_mgr,
785 lights,
786 );
787
788 self.trail_mgr.maintain(renderer, scene_data);
790
791 let max_light_dist = loaded_distance.powi(2) + LIGHT_DIST_RADIUS;
793 lights.extend(
794 (
795 &scene_data.state.ecs().read_storage::<comp::Pos>(),
796 scene_data
797 .state
798 .ecs()
799 .read_storage::<crate::ecs::comp::Interpolated>()
800 .maybe(),
801 &scene_data
802 .state
803 .ecs()
804 .read_storage::<comp::LightAnimation>(),
805 scene_data
806 .state
807 .ecs()
808 .read_storage::<comp::Health>()
809 .maybe(),
810 )
811 .join()
812 .filter(|(pos, _, light_anim, h)| {
813 light_anim.col != Rgb::zero()
814 && light_anim.strength > 0.0
815 && pos.0.distance_squared(viewpoint_pos) < max_light_dist
816 && h.is_none_or(|h| !h.is_dead)
817 })
818 .map(|(pos, interpolated, light_anim, _)| {
819 let pos = interpolated.map_or(pos.0, |i| i.pos);
821 let mut light =
822 Light::new(pos + light_anim.offset, light_anim.col, light_anim.strength);
823 if let Some((dir, fov)) = light_anim.dir {
824 light = light.with_dir(dir, fov);
825 }
826 light
827 })
828 .chain(
829 self.event_lights
830 .iter()
831 .map(|el| el.light.with_strength((el.fadeout)(el.timeout))),
832 ),
833 );
834 let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
835 let figure_mgr = &self.figure_mgr;
836 lights.extend(
837 (
838 &scene_data.state.ecs().entities(),
839 &scene_data
840 .state
841 .read_storage::<crate::ecs::comp::Interpolated>(),
842 &scene_data.state.read_storage::<comp::Body>(),
843 &scene_data.state.read_storage::<comp::Collider>(),
844 )
845 .join()
846 .filter_map(|(entity, interpolated, body, collider)| {
847 let vol = collider.get_vol(&voxel_colliders_manifest)?;
848 let (blocks_of_interest, offset) =
849 figure_mgr.get_blocks_of_interest(entity, body, Some(collider))?;
850
851 let mat = Mat4::from(interpolated.ori.to_quat())
852 .translated_3d(interpolated.pos)
853 * Mat4::translation_3d(offset);
854
855 let p = mat.inverted().mul_point(viewpoint_pos);
856 let aabb = Aabb {
857 min: Vec3::zero(),
858 max: vol.volume().sz.as_(),
859 };
860 if aabb.contains_point(p) || aabb.distance_to_point(p) < max_light_dist {
861 Some(
862 blocks_of_interest
863 .lights
864 .iter()
865 .map(move |(block_offset, level)| {
866 let wpos = mat.mul_point(block_offset.as_() + 0.5);
867 (wpos, level)
868 })
869 .filter(move |(wpos, _)| {
870 wpos.distance_squared(viewpoint_pos) < max_light_dist
871 })
872 .map(|(wpos, level)| {
873 Light::new(wpos, Rgb::white(), *level as f32 / 7.0)
874 }),
875 )
876 } else {
877 None
878 }
879 })
880 .flatten(),
881 );
882 lights.sort_by_key(|light| light.get_pos().distance_squared(viewpoint_pos) as i32);
883 lights.truncate(MAX_LIGHT_COUNT);
884 renderer.update_consts(&mut self.data.lights, lights);
885
886 self.event_lights.retain_mut(|el| {
888 el.timeout -= dt;
889 el.timeout > 0.0
890 });
891
892 let mut shadows = (
894 &scene_data.state.ecs().read_storage::<comp::Pos>(),
895 scene_data
896 .state
897 .ecs()
898 .read_storage::<crate::ecs::comp::Interpolated>()
899 .maybe(),
900 scene_data.state.ecs().read_storage::<comp::Scale>().maybe(),
901 &scene_data.state.ecs().read_storage::<comp::Body>(),
902 &scene_data.state.ecs().read_storage::<comp::Health>(),
903 )
904 .join()
905 .filter(|(_, _, _, _, health)| !health.is_dead)
906 .filter(|(pos, _, _, _, _)| {
907 pos.0.distance_squared(viewpoint_pos)
908 < (loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powi(2)
909 })
910 .map(|(pos, interpolated, scale, _, _)| {
911 Shadow::new(
912 interpolated.map_or(pos.0, |i| i.pos),
914 scale.map_or(1.0, |s| s.0),
915 )
916 })
917 .collect::<Vec<_>>();
918 shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(viewpoint_pos) as i32);
919 shadows.truncate(MAX_SHADOW_COUNT);
920 renderer.update_consts(&mut self.data.shadows, &shadows);
921
922 self.loaded_distance = loaded_distance;
924
925 const DAY: f64 = 60.0 * 60.0 * 24.0;
933 let time_of_day = scene_data.state.get_time_of_day();
934 let max_lerp_period = if scene_data.flashing_lights_enabled {
935 DAY * 2.0
936 } else {
937 DAY * 0.25
938 };
939 self.interpolated_time_of_day =
940 Some(self.interpolated_time_of_day.map_or(time_of_day, |tod| {
941 if (tod - time_of_day).abs() > max_lerp_period {
942 time_of_day
943 } else {
944 Lerp::lerp(tod, time_of_day, dt as f64)
945 }
946 }));
947 let time_of_day = self.interpolated_time_of_day.unwrap_or(time_of_day);
948 let focus_pos = self.camera.get_focus_pos();
949 let focus_off = focus_pos.map(|e| e.trunc());
950
951 let step = 0.5 * dt;
952 self.screen_fade = if step > (self.screen_fade - self.screen_fade_tgt).abs() {
953 self.screen_fade_tgt
954 } else {
955 self.screen_fade + (self.screen_fade_tgt - self.screen_fade).signum() * step
956 };
957
958 let player_dir = client
959 .state()
960 .ecs()
961 .read_storage::<comp::Ori>()
962 .get(client.entity())
963 .copied()
964 .unwrap_or_default()
965 .look_dir()
966 .to_vec();
967
968 let player_mmap_ori = player_dir.x.atan2(player_dir.y)
970 - if !mmap_face_north {
971 self.camera.get_orientation().x
973 } else {
974 0.0
975 };
976
977 renderer.update_consts(&mut self.data.globals, &[Globals::new(
979 view_mat,
980 proj_mat,
981 cam_pos,
982 focus_pos,
983 self.loaded_distance,
984 self.lod.get_data().tgt_detail as f32,
985 self.map_bounds,
986 time_of_day,
987 scene_data.state.get_time(),
988 self.local_time,
989 renderer.resolution().as_(),
990 renderer.internal_resolution().as_(),
991 Vec2::new(SHADOW_NEAR, SHADOW_FAR),
992 lights.len(),
993 shadows.len(),
994 NUM_DIRECTED_LIGHTS,
995 scene_data
996 .state
997 .terrain()
998 .get((cam_pos + focus_off).map(|e| e.floor() as i32))
999 .ok()
1000 .filter(|b| !(b.is_filled() && client.is_moderator()))
1002 .map(|b| b.kind())
1003 .unwrap_or(BlockKind::Air),
1004 self.select_pos.map(|e| e - focus_off.map(|e| e as i32)),
1005 scene_data.gamma,
1006 scene_data.exposure,
1007 self.last_lightning.unwrap_or((Vec3::zero(), -1000.0)),
1008 self.wind_vel,
1009 scene_data.ambiance,
1010 self.camera.get_mode(),
1011 scene_data.sprite_render_distance - 20.0,
1012 player_mmap_ori,
1013 self.screen_fade,
1014 )]);
1015 renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv));
1016 renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
1017
1018 self.lod.maintain(renderer, client, focus_pos, &self.camera);
1020
1021 self.tether_mgr.maintain(renderer, client, focus_pos);
1023
1024 self.debug.maintain(renderer);
1026
1027 let (
1029 _visible_bounds,
1030 visible_light_volume,
1031 visible_psr_bounds,
1032 visible_occlusion_volume,
1033 visible_por_bounds,
1034 ) = self.terrain.maintain(
1035 renderer,
1036 scene_data,
1037 focus_pos,
1038 self.loaded_distance,
1039 &self.camera,
1040 );
1041
1042 let _figure_bounds = self.figure_mgr.maintain(
1044 renderer,
1045 &mut self.trail_mgr,
1046 scene_data,
1047 visible_psr_bounds,
1048 visible_por_bounds,
1049 &self.camera,
1050 Some(&self.terrain),
1051 );
1052
1053 let fov = self.camera.get_effective_fov();
1054 let aspect_ratio = self.camera.get_aspect_ratio();
1055 let view_dir = ((focus_pos.map(f32::fract)) - cam_pos).normalized();
1056
1057 let look_at = cam_pos;
1062 let new_dir = view_dir;
1063 let new_dir = new_dir.normalized();
1064 let up: math::Vec3<f32> = math::Vec3::unit_y();
1065
1066 let texture_mat = Mat4::<f32>::scaling_3d::<Vec3<f32>>(Vec3::new(0.5, -0.5, 1.0))
1080 * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0));
1081
1082 let directed_mats = |d_view_mat: math::Mat4<f32>,
1083 d_dir: math::Vec3<f32>,
1084 volume: &Vec<math::Vec3<f32>>|
1085 -> (Mat4<f32>, Mat4<f32>) {
1086 let v_p_orig = math::Vec3::from(d_view_mat * math::Vec4::from_direction(new_dir));
1088 let mut v_p = v_p_orig.normalized();
1089 let cos_gamma = new_dir.map(f64::from).dot(d_dir.map(f64::from));
1090 let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt();
1091 let gamma = sin_gamma.asin();
1092 let view_mat = math::Mat4::from_col_array(view_mat.into_col_array());
1093 let bounds1 = math::fit_psr(
1096 view_mat.map_cols(math::Vec4::from),
1097 volume.iter().copied(),
1098 math::Vec4::homogenized,
1099 );
1100 let n_e = f64::from(-bounds1.max.z);
1101 let factor = compute_warping_parameter_perspective(
1102 gamma,
1103 n_e,
1104 f64::from(fov),
1105 f64::from(aspect_ratio),
1106 );
1107
1108 v_p.z = 0.0;
1109 v_p.normalize();
1110 let l_r: math::Mat4<f32> = if factor > EPSILON_UPSILON {
1111 math::Mat4::look_at_lh(math::Vec3::zero(), math::Vec3::unit_z(), v_p)
1115 } else {
1116 math::Mat4::identity()
1117 };
1118 let directed_proj_mat = math::Mat4::new(
1120 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,
1121 );
1122
1123 let light_all_mat = l_r * directed_proj_mat * d_view_mat;
1124 let bounds0 = math::fit_psr(
1127 light_all_mat,
1128 volume.iter().copied(),
1129 math::Vec4::homogenized,
1130 );
1131 let (z_0, z_1) = {
1139 let f_e = f64::from(-bounds1.min.z).max(n_e);
1140 let p_z = bounds1.max.z;
1142 let p_y = bounds0.min.y;
1144 let p_x = bounds0.center().x;
1145 let view_inv = view_mat.inverted();
1147 let light_all_inv = light_all_mat.inverted();
1149
1150 let view_point = view_inv
1152 * math::Vec4::from_point(
1153 -math::Vec3::unit_z() * p_z, );
1155 let view_plane = view_mat.transposed() * -math::Vec4::unit_z();
1156
1157 let light_point = light_all_inv
1159 * math::Vec4::from_point(
1160 math::Vec3::unit_y() * p_y, );
1162 let light_plane = light_all_mat.transposed() * math::Vec4::unit_y();
1163
1164 let shadow_point = light_all_inv
1166 * math::Vec4::from_point(
1167 math::Vec3::unit_x() * p_x, );
1169 let shadow_plane = light_all_mat.transposed() * math::Vec4::unit_x();
1170
1171 let solve_p0 = math::Mat4::new(
1175 view_plane.x,
1176 view_plane.y,
1177 view_plane.z,
1178 0.0,
1179 light_plane.x,
1180 light_plane.y,
1181 light_plane.z,
1182 0.0,
1183 shadow_plane.x,
1184 shadow_plane.y,
1185 shadow_plane.z,
1186 0.0,
1187 0.0,
1188 0.0,
1189 0.0,
1190 1.0,
1191 );
1192
1193 let plane_dist = math::Vec4::new(
1195 view_plane.dot(view_point),
1196 light_plane.dot(light_point),
1197 shadow_plane.dot(shadow_point),
1198 1.0,
1199 );
1200 let p0_world = solve_p0.inverted() * plane_dist;
1201 let p0 = light_all_mat * p0_world;
1203 let mut p1 = p0;
1204 p1.y = bounds0.max.y;
1206
1207 let view_from_light_mat = view_mat * light_all_inv;
1210 let z0 = view_from_light_mat * p0;
1212 let z1 = view_from_light_mat * p1;
1213
1214 (
1219 f64::from(z0.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
1220 f64::from(z1.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
1221 )
1222 };
1223
1224 let mut light_focus_pos: math::Vec3<f32> = math::Vec3::zero();
1226 light_focus_pos.x = bounds0.center().x;
1227 light_focus_pos.y = bounds0.min.y;
1228 light_focus_pos.z = bounds0.center().z;
1229
1230 let d = f64::from(bounds0.max.y - bounds0.min.y).abs();
1231
1232 let w_l_y = d;
1233
1234 let alpha = z_1 / z_0;
1238 let alpha_sqrt = alpha.sqrt();
1239 let directed_near_normal = if factor < 0.0 {
1240 (1.0 + alpha_sqrt - factor * (alpha - 1.0)) / ((alpha - 1.0) * (factor + 1.0))
1242 } else {
1243 ((alpha_sqrt - 1.0) * (factor * alpha_sqrt + 1.0)).recip()
1245 };
1246
1247 let y_ = |v: f64| w_l_y * (v + directed_near_normal).abs();
1249 let directed_near = y_(0.0) as f32;
1250 let directed_far = y_(1.0) as f32;
1251 light_focus_pos.y = if factor > EPSILON_UPSILON {
1252 light_focus_pos.y - directed_near
1253 } else {
1254 light_focus_pos.y
1255 };
1256 let w_v: math::Mat4<f32> = math::Mat4::translation_3d(-math::Vec3::new(
1258 light_focus_pos.x,
1259 light_focus_pos.y,
1260 light_focus_pos.z,
1261 ));
1262 let shadow_view_mat: math::Mat4<f32> = w_v * light_all_mat;
1263 let w_p: math::Mat4<f32> = {
1264 if factor > EPSILON_UPSILON {
1265 let near = directed_near;
1267 let far = directed_far;
1268 let left = -1.0;
1269 let right = 1.0;
1270 let bottom = -1.0;
1271 let top = 1.0;
1272 let s_x = 2.0 * near / (right - left);
1273 let o_x = (right + left) / (right - left);
1274 let s_z = 2.0 * near / (top - bottom);
1275 let o_z = (top + bottom) / (top - bottom);
1276
1277 let s_y = (far + near) / (far - near);
1278 let o_y = -2.0 * far * near / (far - near);
1279
1280 math::Mat4::new(
1281 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,
1282 0.0,
1283 )
1284 } else {
1285 math::Mat4::identity()
1286 }
1287 };
1288
1289 let shadow_all_mat: math::Mat4<f32> = w_p * shadow_view_mat;
1290 let math::Aabb::<f32> {
1293 min:
1294 math::Vec3 {
1295 x: xmin,
1296 y: ymin,
1297 z: zmin,
1298 },
1299 max:
1300 math::Vec3 {
1301 x: xmax,
1302 y: ymax,
1303 z: zmax,
1304 },
1305 } = math::fit_psr(
1306 shadow_all_mat,
1307 volume.iter().copied(),
1308 math::Vec4::homogenized,
1309 );
1310 let s_x = 2.0 / (xmax - xmin);
1311 let s_y = 2.0 / (ymax - ymin);
1312 let s_z = 1.0 / (zmax - zmin);
1313 let o_x = -(xmax + xmin) / (xmax - xmin);
1314 let o_y = -(ymax + ymin) / (ymax - ymin);
1315 let o_z = -zmin / (zmax - zmin);
1316 let directed_proj_mat = Mat4::new(
1317 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,
1318 );
1319
1320 let shadow_all_mat: Mat4<f32> = Mat4::from_col_arrays(shadow_all_mat.into_col_arrays());
1321
1322 let directed_texture_proj_mat = texture_mat * directed_proj_mat;
1323 (
1324 directed_proj_mat * shadow_all_mat,
1325 directed_texture_proj_mat * shadow_all_mat,
1326 )
1327 };
1328
1329 let weather = client
1330 .state()
1331 .max_weather_near(focus_off.xy() + cam_pos.xy());
1332 self.wind_vel = weather.wind_vel();
1333 if weather.rain > RAIN_THRESHOLD {
1334 let weather = client.weather_at_player();
1335 let rain_vel = weather.rain_vel();
1336 let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);
1337
1338 self.integrated_rain_vel += rain_vel.magnitude() * dt;
1339 let rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), rain_vel);
1340
1341 let (shadow_mat, texture_mat) =
1342 directed_mats(rain_view_mat, rain_vel, &visible_occlusion_volume);
1343
1344 let rain_occlusion_locals = RainOcclusionLocals::new(
1345 shadow_mat,
1346 texture_mat,
1347 rain_dir_mat,
1348 weather.rain,
1349 self.integrated_rain_vel,
1350 );
1351
1352 renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
1353 } else if self.integrated_rain_vel > 0.0 {
1354 self.integrated_rain_vel = 0.0;
1355 let rain_occlusion_locals = RainOcclusionLocals::default();
1357 renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
1358 }
1359
1360 let sun_dir = scene_data.get_sun_dir();
1361 let is_daylight = sun_dir.z < 0.0;
1362 if renderer.pipeline_modes().shadow.is_map() && (is_daylight || !lights.is_empty()) {
1363 let (point_shadow_res, _directed_shadow_res) = renderer.get_shadow_resolution();
1364 let point_shadow_aspect = point_shadow_res.x as f32 / point_shadow_res.y as f32;
1367 let directed_light_dir = sun_dir;
1370
1371 let mut directed_shadow_mats = Vec::with_capacity(6);
1374
1375 let light_view_mat = math::Mat4::look_at_rh(look_at, look_at + directed_light_dir, up);
1376 let (shadow_mat, texture_mat) =
1377 directed_mats(light_view_mat, directed_light_dir, &visible_light_volume);
1378
1379 let shadow_locals = ShadowLocals::new(shadow_mat, texture_mat);
1380
1381 renderer.update_consts(&mut self.data.shadow_mats, &[shadow_locals]);
1382
1383 directed_shadow_mats.push(light_view_mat);
1384 directed_shadow_mats
1386 .extend_from_slice(&[math::Mat4::default(); 6 - NUM_DIRECTED_LIGHTS] as _);
1387 let mut shadow_mats = Vec::with_capacity(6 * (lights.len() + 1));
1390 shadow_mats.resize_with(6, PointLightMatrix::default);
1391 let shadow_proj = camera::perspective_rh_zo_general(
1397 90.0f32.to_radians(),
1398 point_shadow_aspect,
1399 1.0 / SHADOW_NEAR,
1400 1.0 / SHADOW_FAR,
1401 );
1402 let shadow_proj = shadow_proj * Mat4::scaling_3d(-1.0);
1407
1408 let orientations = [
1411 (Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
1412 (Vec3::new(-1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
1413 (Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 0.0, 1.0)),
1414 (Vec3::new(0.0, -1.0, 0.0), Vec3::new(0.0, 0.0, -1.0)),
1415 (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.0, -1.0, 0.0)),
1416 (Vec3::new(0.0, 0.0, -1.0), Vec3::new(0.0, -1.0, 0.0)),
1417 ];
1418
1419 shadow_mats.extend(lights.iter().flat_map(|light| {
1423 let eye = Vec3::new(light.pos[0], light.pos[1], light.pos[2]) - focus_off;
1426 orientations.iter().map(move |&(forward, up)| {
1427 PointLightMatrix::new(shadow_proj * Mat4::look_at_lh(eye, eye + forward, up))
1430 })
1431 }));
1432
1433 for (i, val) in shadow_mats.into_iter().enumerate() {
1434 self.data.point_light_matrices[i] = val
1435 }
1436 }
1437
1438 self.figure_mgr.clean(scene_data.tick);
1440
1441 self.sfx_mgr.maintain(
1443 audio,
1444 scene_data.state,
1445 scene_data.viewpoint_entity,
1446 &self.camera,
1447 &self.terrain,
1448 client,
1449 &self.figure_mgr,
1450 );
1451
1452 self.ambience_mgr.maintain(
1453 audio,
1454 &settings.audio,
1455 scene_data.state,
1456 client,
1457 &self.camera,
1458 &self.terrain,
1459 );
1460
1461 self.music_mgr.maintain(audio, scene_data.state, client);
1462 }
1463
1464 pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
1465
1466 pub fn render(
1468 &self,
1469 drawer: &mut Drawer<'_>,
1470 state: &State,
1471 viewpoint_entity: EcsEntity,
1472 tick: u64,
1473 scene_data: &SceneData,
1474 ) {
1475 span!(_guard, "render", "Scene::render");
1476 let sun_dir = scene_data.get_sun_dir();
1477 let is_daylight = sun_dir.z < 0.0;
1478 let focus_pos = self.camera.get_focus_pos();
1479 let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
1480 let is_rain = state.max_weather_near(cam_pos.xy()).rain > RAIN_THRESHOLD;
1481 let culling_mode = if scene_data
1482 .state
1483 .terrain()
1484 .get_key(scene_data.state.terrain().pos_key(cam_pos.as_()))
1485 .is_some_and(|c| cam_pos.z < c.meta().alt() - terrain::UNDERGROUND_ALT)
1486 {
1487 CullingMode::Underground
1488 } else {
1489 CullingMode::Surface
1490 };
1491
1492 let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
1493
1494 if drawer.pipeline_modes().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) {
1496 if is_daylight {
1497 prof_span!("directed shadows");
1498 if let Some(mut shadow_pass) = drawer.shadow_pass() {
1499 self.terrain.render_shadows(
1501 &mut shadow_pass.draw_terrain_shadows(),
1502 focus_pos,
1503 culling_mode,
1504 );
1505
1506 self.figure_mgr.render_shadows(
1508 &mut shadow_pass.draw_figure_shadows(),
1509 state,
1510 tick,
1511 camera_data,
1512 );
1513 self.debug
1514 .render_shadows(&mut shadow_pass.draw_debug_shadows());
1515 }
1516 }
1517
1518 {
1520 prof_span!("point shadows");
1521 drawer.draw_point_shadows(
1522 &self.data.point_light_matrices,
1523 self.terrain.chunks_for_point_shadows(focus_pos),
1524 )
1525 }
1526 }
1527 if is_rain {
1529 prof_span!("rain occlusion");
1530 if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
1531 self.terrain
1532 .render_rain_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
1533
1534 self.figure_mgr.render_rain_occlusion(
1535 &mut occlusion_pass.draw_figure_shadows(),
1536 state,
1537 tick,
1538 camera_data,
1539 );
1540 }
1541 }
1542
1543 prof_span!(guard, "main pass");
1544 if let Some(mut first_pass) = drawer.first_pass() {
1545 self.figure_mgr.render_viewpoint(
1546 &mut first_pass.draw_figures(),
1547 state,
1548 viewpoint_entity,
1549 tick,
1550 camera_data,
1551 );
1552
1553 self.terrain
1554 .render(&mut first_pass, focus_pos, culling_mode);
1555
1556 self.figure_mgr.render(
1557 &mut first_pass.draw_figures(),
1558 state,
1559 viewpoint_entity,
1560 tick,
1561 camera_data,
1562 );
1563
1564 self.lod.render(&mut first_pass, culling_mode);
1565
1566 first_pass.draw_skybox(&self.skybox.model);
1568
1569 let mut sprite_drawer = first_pass.draw_sprites(
1571 &self.terrain.sprite_globals,
1572 &self.terrain.sprite_render_state.sprite_atlas_textures,
1573 );
1574 self.figure_mgr.render_sprites(
1575 &mut sprite_drawer,
1576 state,
1577 cam_pos,
1578 scene_data.sprite_render_distance,
1579 );
1580 self.terrain.render_sprites(
1581 &mut sprite_drawer,
1582 focus_pos,
1583 cam_pos,
1584 scene_data.sprite_render_distance,
1585 culling_mode,
1586 );
1587 drop(sprite_drawer);
1588
1589 self.tether_mgr.render(&mut first_pass);
1591
1592 self.particle_mgr
1594 .render(&mut first_pass.draw_particles(), scene_data);
1595
1596 self.terrain.render_translucent(&mut first_pass, focus_pos);
1598
1599 self.debug.render(&mut first_pass.draw_debug());
1601 }
1602 drop(guard);
1603 }
1604
1605 pub fn maintain_debug_hitboxes(
1606 &mut self,
1607 client: &Client,
1608 settings: &Settings,
1609 hitboxes: &mut HashMap<specs::Entity, DebugShapeId>,
1610 tracks: &mut HashMap<Vec2<i32>, Vec<DebugShapeId>>,
1611 gizmos: &mut Vec<(DebugShapeId, common::resources::Time, bool)>,
1612 ) {
1613 let ecs = client.state().ecs();
1614 {
1615 let mut current_chunks = hashbrown::HashSet::new();
1616 let terrain_grid = ecs.read_resource::<TerrainGrid>();
1617 for (key, chunk) in terrain_grid.iter() {
1618 current_chunks.insert(key);
1619 tracks.entry(key).or_insert_with(|| {
1620 let mut ret = Vec::new();
1621 for bezier in chunk.meta().tracks().iter() {
1622 let shape_id = self.debug.add_shape(DebugShape::TrainTrack {
1623 path: *bezier,
1624 rail_width: 0.35,
1625 rail_sep: 2.5,
1626 plank_width: 0.75,
1627 plank_height: 0.25,
1628 plank_sep: 6.0,
1629 });
1630 ret.push(shape_id);
1631 self.debug
1632 .set_context(shape_id, [0.0; 4], [1.0; 4], [0.0, 0.0, 0.0, 1.0]);
1633 }
1634 for point in chunk.meta().debug_points().iter() {
1635 let shape_id = self.debug.add_shape(DebugShape::Cylinder {
1636 radius: 0.1,
1637 height: 0.1,
1638 });
1639 ret.push(shape_id);
1640 self.debug.set_context(
1641 shape_id,
1642 point.with_w(0.0).into_array(),
1643 [1.0; 4],
1644 [0.0, 0.0, 0.0, 1.0],
1645 );
1646 }
1647 for line in chunk.meta().debug_lines().iter() {
1648 let shape_id = self
1649 .debug
1650 .add_shape(DebugShape::Line([line.start, line.end], 0.1));
1651 ret.push(shape_id);
1652 self.debug
1653 .set_context(shape_id, [0.0; 4], [1.0; 4], [0.0, 0.0, 0.0, 1.0]);
1654 }
1655 ret
1656 });
1657 }
1658 tracks.retain(|k, v| {
1659 let keep = current_chunks.contains(k);
1660 if !keep {
1661 for shape in v.iter() {
1662 self.debug.remove_shape(*shape);
1663 }
1664 }
1665 keep
1666 });
1667 }
1668 let mut current_entities = hashbrown::HashSet::new();
1669 if settings.interface.toggle_hitboxes {
1670 let positions = ecs.read_component::<comp::Pos>();
1671 let colliders = ecs.read_component::<comp::Collider>();
1672 let orientations = ecs.read_component::<comp::Ori>();
1673 let scales = ecs.read_component::<comp::Scale>();
1674 let groups = ecs.read_component::<comp::Group>();
1675 let bodies = ecs.read_component::<comp::Body>();
1676 for (entity, pos, collider, ori, body, scale, group) in (
1677 &ecs.entities(),
1678 &positions,
1679 &colliders,
1680 &orientations,
1681 &bodies,
1682 scales.maybe(),
1683 groups.maybe(),
1684 )
1685 .join()
1686 {
1687 match collider {
1688 comp::Collider::CapsulePrism(CapsulePrism {
1689 p0,
1690 p1,
1691 radius,
1692 z_min,
1693 z_max,
1694 }) => {
1695 let scale = scale.map_or(1.0, |s| s.0);
1696 current_entities.insert(entity);
1697
1698 let shape = DebugShape::CapsulePrism {
1699 p0: *p0 * scale,
1700 p1: *p1 * scale,
1701 radius: *radius * scale,
1702 head_ratio: body.top_ratio(),
1703 height: (*z_max - *z_min) * scale,
1704 };
1705
1706 if let Some(shape_id) = hitboxes.get(&entity)
1708 && self.debug.get_shape(*shape_id).is_some_and(|s| s != &shape)
1709 {
1710 self.debug.remove_shape(*shape_id);
1711 hitboxes.remove(&entity);
1712 }
1713
1714 let shape_id = hitboxes
1715 .entry(entity)
1716 .or_insert_with(|| self.debug.add_shape(shape));
1717 let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min * scale, 0.0];
1718 let color = if group == Some(&comp::group::ENEMY) {
1719 [1.0, 0.0, 0.0, 0.5]
1720 } else if group == Some(&comp::group::NPC) {
1721 [0.0, 0.0, 1.0, 0.5]
1722 } else {
1723 [0.0, 1.0, 0.0, 0.5]
1724 };
1725 let ori = ori.to_quat();
1727 let hb_ori = [ori.x, ori.y, ori.z, ori.w];
1728 self.debug.set_context(*shape_id, hb_pos, color, hb_ori);
1729 },
1730 comp::Collider::Voxel { .. }
1731 | comp::Collider::Volume(_)
1732 | comp::Collider::Point => {
1733 },
1735 }
1736 }
1737 }
1738 hitboxes.retain(|k, v| {
1739 let keep = current_entities.contains(k);
1740 if !keep {
1741 self.debug.remove_shape(*v);
1742 }
1743 keep
1744 });
1745
1746 let time = client.state().get_time();
1747 gizmos.retain(|(id, end_time, _)| {
1748 let keep = end_time.0 > time;
1749 if !keep {
1750 self.debug.remove_shape(*id);
1751 }
1752 keep
1753 });
1754 }
1755
1756 pub fn maintain_debug_vectors(&mut self, client: &Client, lines: &mut PlayerDebugLines) {
1757 lines
1758 .chunk_normal
1759 .take()
1760 .map(|id| self.debug.remove_shape(id));
1761 lines.fluid_vel.take().map(|id| self.debug.remove_shape(id));
1762 lines.wind.take().map(|id| self.debug.remove_shape(id));
1763 lines.vel.take().map(|id| self.debug.remove_shape(id));
1764 if self.debug_vectors_enabled {
1765 let ecs = client.state().ecs();
1766
1767 let vels = &ecs.read_component::<comp::Vel>();
1768 let Some(vel) = vels.get(client.entity()) else {
1769 return;
1770 };
1771
1772 let phys_states = &ecs.read_component::<comp::PhysicsState>();
1773 let Some(phys) = phys_states.get(client.entity()) else {
1774 return;
1775 };
1776
1777 let positions = &ecs.read_component::<comp::Pos>();
1778 let Some(pos) = positions.get(client.entity()) else {
1779 return;
1780 };
1781
1782 let weather = ecs.read_resource::<WeatherGrid>();
1783 const LINE_WIDTH: f32 = 0.05;
1786 {
1788 let Some(fluid) = phys.in_fluid else {
1789 return;
1790 };
1791 let shape = DebugShape::Line([pos.0, pos.0 + fluid.flow_vel().0 / 2.], LINE_WIDTH);
1792 let id = self.debug.add_shape(shape);
1793 lines.fluid_vel = Some(id);
1794 self.debug
1795 .set_context(id, [0.0; 4], [0.18, 0.72, 0.87, 0.8], [0.0, 0.0, 0.0, 1.0]);
1796 }
1797 {
1799 let Some(chunk) = client.current_chunk() else {
1800 return;
1801 };
1802 let shape = DebugShape::Line(
1803 [
1804 pos.0,
1805 pos.0
1806 + chunk
1807 .meta()
1808 .approx_chunk_terrain_normal()
1809 .unwrap_or(Vec3::unit_z())
1810 * 2.5,
1811 ],
1812 LINE_WIDTH,
1813 );
1814 let id = self.debug.add_shape(shape);
1815 lines.chunk_normal = Some(id);
1816 self.debug
1817 .set_context(id, [0.0; 4], [0.22, 0.63, 0.1, 0.8], [0.0, 0.0, 0.0, 1.0]);
1818 }
1819 {
1821 let wind = weather.get_interpolated(pos.0.xy()).wind_vel();
1822 let shape = DebugShape::Line([pos.0, pos.0 + wind * 5.0], LINE_WIDTH);
1823 let id = self.debug.add_shape(shape);
1824 lines.wind = Some(id);
1825 self.debug
1826 .set_context(id, [0.0; 4], [0.76, 0.76, 0.76, 0.8], [0.0, 0.0, 0.0, 1.0]);
1827 }
1828 {
1830 let shape = DebugShape::Line([pos.0, pos.0 + vel.0 / 2.0], LINE_WIDTH);
1831 let id = self.debug.add_shape(shape);
1832 lines.vel = Some(id);
1833 self.debug
1834 .set_context(id, [0.0; 4], [0.98, 0.76, 0.01, 0.8], [0.0, 0.0, 0.0, 1.0]);
1835 }
1836 }
1837 }
1838}