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