1pub mod camera;
2pub mod debug;
3pub mod figure;
4pub mod lod;
5pub mod math;
6pub mod particle;
7pub mod simple;
8pub mod smoke_cycle;
9pub mod terrain;
10pub mod tether;
11pub mod trail;
12
13use std::collections::HashSet;
14
15pub use self::{
16 camera::{Camera, CameraMode},
17 debug::{Debug, DebugShape, DebugShapeId},
18 figure::FigureMgr,
19 lod::Lod,
20 particle::ParticleMgr,
21 terrain::{SpriteRenderContextLazy, Terrain},
22 tether::TetherMgr,
23 trail::TrailMgr,
24};
25use crate::{
26 audio::{
27 AudioFrontend,
28 ambience::{self, AmbienceMgr},
29 music::MusicMgr,
30 sfx::SfxMgr,
31 },
32 render::{
33 CloudsLocals, Consts, CullingMode, Drawer, GlobalModel, Globals, GlobalsBindGroup, Light,
34 Model, PointLightMatrix, PostProcessLocals, RainOcclusionLocals, Renderer, Shadow,
35 ShadowLocals, SkyboxVertex, create_skybox_mesh,
36 },
37 session::PlayerDebugLines,
38 settings::Settings,
39 window::{AnalogGameInput, Event},
40};
41use client::Client;
42use common::{
43 calendar::Calendar,
44 comp::{
45 self, CharacterState, item::ItemDesc, ship::figuredata::VOXEL_COLLIDER_MANIFEST,
46 slot::EquipSlot, tool::ToolKind,
47 },
48 outcome::Outcome,
49 resources::{DeltaTime, TimeOfDay, TimeScale},
50 terrain::{BlockKind, TerrainChunk, TerrainGrid},
51 vol::ReadVol,
52 weather::WeatherGrid,
53};
54use common_base::{prof_span, span};
55use common_state::State;
56use comp::item::Reagent;
57use hashbrown::HashMap;
58use num::traits::{Float, FloatConst};
59use specs::{Entity as EcsEntity, Join, LendJoin, WorldExt};
60use vek::*;
61
62const ZOOM_CAP_PLAYER: f32 = 1000.0;
63const ZOOM_CAP_ADMIN: f32 = 100000.0;
64
65const CURSOR_PAN_SCALE: f32 = 0.005;
67
68pub(crate) const MAX_LIGHT_COUNT: usize = 20; pub(crate) const MAX_SHADOW_COUNT: usize = 24;
71pub(crate) const MAX_POINT_LIGHT_MATRICES_COUNT: usize = MAX_LIGHT_COUNT * 6 + 6;
72const NUM_DIRECTED_LIGHTS: usize = 1;
73const LIGHT_DIST_RADIUS: f32 = 64.0; const SHADOW_DIST_RADIUS: f32 = 8.0;
75const SHADOW_MAX_DIST: f32 = 96.0; const EPSILON_UPSILON: f64 = -1.0;
78
79const SHADOW_NEAR: f32 = 0.25; const SHADOW_FAR: f32 = 128.0; const RUNNING_THRESHOLD: f32 = 0.7;
85
86const RAIN_THRESHOLD: f32 = 0.0;
88
89pub type LightData<'a> = (bool, &'a [Light]);
91
92struct EventLight {
93 light: Light,
94 timeout: f32,
95 fadeout: fn(f32) -> f32,
96}
97
98struct Skybox {
99 model: Model<SkyboxVertex>,
100}
101
102pub struct Scene {
103 data: GlobalModel,
104 globals_bind_group: GlobalsBindGroup,
105 camera: Camera,
106 camera_input_state: Vec2<f32>,
107 event_lights: Vec<EventLight>,
108
109 skybox: Skybox,
110 terrain: Terrain<TerrainChunk>,
111 pub debug: Debug,
112 pub lod: Lod,
113 loaded_distance: f32,
114 map_bounds: Vec2<f32>,
118 select_pos: Option<Vec3<i32>>,
119 light_data: Vec<Light>,
120
121 particle_mgr: ParticleMgr,
122 trail_mgr: TrailMgr,
123 figure_mgr: FigureMgr,
124 tether_mgr: TetherMgr,
125 pub sfx_mgr: SfxMgr,
126 pub music_mgr: MusicMgr,
127 ambience_mgr: AmbienceMgr,
128
129 integrated_rain_vel: f32,
130 wind_vel: Vec2<f32>,
131 pub interpolated_time_of_day: Option<f64>,
132 last_lightning: Option<(Vec3<f32>, f64)>,
133 local_time: f64,
134
135 pub debug_vectors_enabled: bool,
136}
137
138pub struct SceneData<'a> {
139 pub client: &'a Client,
140 pub state: &'a State,
141 pub viewpoint_entity: specs::Entity,
142 pub mutable_viewpoint: bool,
143 pub target_entities: &'a HashSet<specs::Entity>,
144 pub loaded_distance: f32,
145 pub terrain_view_distance: u32, pub entity_view_distance: u32,
147 pub tick: u64,
148 pub gamma: f32,
149 pub exposure: f32,
150 pub ambiance: f32,
151 pub mouse_smoothing: bool,
152 pub sprite_render_distance: f32,
153 pub particles_enabled: bool,
154 pub weapon_trails_enabled: bool,
155 pub flashing_lights_enabled: bool,
156 pub figure_lod_render_distance: f32,
157 pub is_aiming: bool,
158 pub interpolated_time_of_day: Option<f64>,
159}
160
161impl SceneData<'_> {
162 pub fn get_sun_dir(&self) -> Vec3<f32> {
163 TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_sun_dir()
164 }
165
166 pub fn get_moon_dir(&self) -> Vec3<f32> {
167 TimeOfDay::new(self.interpolated_time_of_day.unwrap_or(0.0)).get_moon_dir()
168 }
169}
170
171fn compute_scalar_fov<F: Float>(_near_plane: F, fov: F, aspect: F) -> F {
212 let two = F::one() + F::one();
213 let theta_y = fov / two;
214 let theta_x = (aspect * theta_y.tan()).atan();
215 theta_x.min(theta_y)
216}
217
218fn compute_warping_parameter<F: Float + FloatConst>(
250 gamma: F,
251 (gamma_a, gamma_b, gamma_c): (F, F, F),
252 (eta_b, eta_c): (F, F),
253) -> F {
254 if gamma < gamma_a {
255 -F::one()
256 } else if gamma_a <= gamma && gamma < gamma_b {
258 -F::one() + (eta_b + F::one()) * (F::one() - (F::FRAC_PI_2() * (gamma - gamma_a) / (gamma_b - gamma_a)).cos())
260 } else if gamma_b <= gamma && gamma < gamma_c {
262 eta_b + (eta_c - eta_b) * (F::FRAC_PI_2() * (gamma - gamma_b) / (gamma_c - gamma_b)).sin()
263 } else {
264 eta_c
265 }
266 .max(-F::one()).min(F::one())
268}
269
270fn compute_warping_parameter_perspective<F: Float + FloatConst>(
285 gamma: F,
286 near_plane: F,
287 fov: F,
288 aspect: F,
289) -> F {
290 let theta = compute_scalar_fov(near_plane, fov, aspect);
291 let two = F::one() + F::one();
292 let three = two + F::one();
293 let ten = three + three + three + F::one();
294 compute_warping_parameter(
295 gamma,
296 (
297 theta / three,
298 theta,
299 theta + (three / ten) * (F::FRAC_PI_2() - theta),
300 ),
301 (-two / ten, F::zero()),
302 )
303}
304
305impl Scene {
306 pub fn new(
308 renderer: &mut Renderer,
309 lazy_init: &mut SpriteRenderContextLazy,
310 client: &Client,
311 settings: &Settings,
312 ) -> Self {
313 let resolution = renderer.resolution().map(|e| e as f32);
314 let sprite_render_context = lazy_init(renderer);
315
316 let data = GlobalModel {
317 globals: renderer.create_consts(&[Globals::default()]),
318 lights: renderer.create_consts(&[Light::default(); MAX_LIGHT_COUNT]),
319 shadows: renderer.create_consts(&[Shadow::default(); MAX_SHADOW_COUNT]),
320 shadow_mats: renderer.create_shadow_bound_locals(&[ShadowLocals::default()]),
321 rain_occlusion_mats: renderer
322 .create_rain_occlusion_bound_locals(&[RainOcclusionLocals::default()]),
323 point_light_matrices: Box::new(
324 [PointLightMatrix::default(); MAX_POINT_LIGHT_MATRICES_COUNT],
325 ),
326 };
327
328 let lod = Lod::new(renderer, client, settings);
329
330 let globals_bind_group = renderer.bind_globals(&data, lod.get_data());
331
332 let terrain = Terrain::new(renderer, &data, lod.get_data(), sprite_render_context);
333
334 let camera_mode = match client.presence() {
335 Some(comp::PresenceKind::Spectator) => CameraMode::Freefly,
336 _ => CameraMode::ThirdPerson,
337 };
338
339 let calendar = client.state().ecs().read_resource::<Calendar>();
340
341 Self {
342 data,
343 globals_bind_group,
344 camera: Camera::new(resolution.x / resolution.y, camera_mode),
345 camera_input_state: Vec2::zero(),
346 event_lights: Vec::new(),
347
348 skybox: Skybox {
349 model: renderer.create_model(&create_skybox_mesh()).unwrap(),
350 },
351 terrain,
352 debug: Debug::new(),
353 lod,
354 loaded_distance: 0.0,
355 map_bounds: Vec2::new(
356 client.world_data().min_chunk_alt(),
357 client.world_data().max_chunk_alt(),
358 ),
359 select_pos: None,
360 light_data: Vec::new(),
361 particle_mgr: ParticleMgr::new(renderer),
362 trail_mgr: TrailMgr::default(),
363 figure_mgr: FigureMgr::new(renderer),
364 tether_mgr: TetherMgr::new(renderer),
365 sfx_mgr: SfxMgr::default(),
366 music_mgr: MusicMgr::new(&calendar),
367 ambience_mgr: AmbienceMgr {
368 ambience: ambience::load_ambience_items(),
369 },
370 integrated_rain_vel: 0.0,
371 wind_vel: Vec2::zero(),
372 interpolated_time_of_day: None,
373 last_lightning: None,
374 local_time: 0.0,
375 debug_vectors_enabled: false,
376 }
377 }
378
379 pub fn globals(&self) -> &Consts<Globals> { &self.data.globals }
381
382 pub fn camera(&self) -> &Camera { &self.camera }
384
385 pub fn terrain(&self) -> &Terrain<TerrainChunk> { &self.terrain }
387
388 pub fn lights(&self) -> &Vec<Light> { &self.light_data }
390
391 pub fn particle_mgr(&self) -> &ParticleMgr { &self.particle_mgr }
393
394 pub fn trail_mgr(&self) -> &TrailMgr { &self.trail_mgr }
396
397 pub fn figure_mgr(&self) -> &FigureMgr { &self.figure_mgr }
399
400 pub fn music_mgr(&self) -> &MusicMgr { &self.music_mgr }
401
402 pub fn camera_mut(&mut self) -> &mut Camera { &mut self.camera }
404
405 pub fn set_select_pos(&mut self, pos: Option<Vec3<i32>>) { self.select_pos = pos; }
407
408 pub fn select_pos(&self) -> Option<Vec3<i32>> { self.select_pos }
409
410 pub fn handle_input_event(&mut self, event: Event, client: &Client) -> bool {
415 match event {
416 Event::Resize(dims) => {
418 self.camera.set_aspect_ratio(dims.x as f32 / dims.y as f32);
419 true
420 },
421 Event::CursorPan(delta) => {
423 self.camera.rotate_by(Vec3::from(delta) * CURSOR_PAN_SCALE);
424 true
425 },
426 Event::Zoom(delta) => {
428 let cap = if client.is_moderator() {
429 ZOOM_CAP_ADMIN
430 } else {
431 ZOOM_CAP_PLAYER
432 };
433 let player_scale = client
437 .state()
438 .read_component_copied::<comp::Scale>(client.entity())
439 .map_or(1.0, |s| s.0);
440 if delta < 0.0 {
441 self.camera.zoom_switch(
442 delta * (0.05 + self.camera.get_distance() * 0.01) / (1.0 - delta * 0.01),
444 cap,
445 player_scale,
446 );
447 } else {
448 self.camera.zoom_switch(
449 delta * (0.05 + self.camera.get_distance() * 0.01),
450 cap,
451 player_scale,
452 );
453 }
454 true
455 },
456 Event::AnalogGameInput(input) => match input {
457 AnalogGameInput::CameraX(d) => {
458 self.camera_input_state.x = d;
459 true
460 },
461 AnalogGameInput::CameraY(d) => {
462 self.camera_input_state.y = d;
463 true
464 },
465 _ => false,
466 },
467 _ => false,
469 }
470 }
471
472 pub fn handle_outcome(
473 &mut self,
474 outcome: &Outcome,
475 scene_data: &SceneData,
476 audio: &mut AudioFrontend,
477 ) {
478 span!(_guard, "handle_outcome", "Scene::handle_outcome");
479 self.particle_mgr
480 .handle_outcome(outcome, scene_data, &self.figure_mgr);
481 self.sfx_mgr
482 .handle_outcome(outcome, audio, scene_data.client);
483
484 match outcome {
485 Outcome::Lightning { pos } => {
486 self.last_lightning = Some((*pos, scene_data.state.get_time()));
487 },
488 Outcome::Explosion {
489 pos,
490 power,
491 is_attack,
492 reagent,
493 ..
494 } => self.event_lights.push(EventLight {
495 light: Light::new(
496 *pos,
497 match reagent {
498 Some(Reagent::Blue) => Rgb::new(0.15, 0.4, 1.0),
499 Some(Reagent::Green) => Rgb::new(0.0, 1.0, 0.0),
500 Some(Reagent::Purple) => Rgb::new(0.7, 0.0, 1.0),
501 Some(Reagent::Red) => {
502 if *is_attack {
503 Rgb::new(1.0, 0.5, 0.0)
504 } else {
505 Rgb::new(1.0, 0.0, 0.0)
506 }
507 },
508 Some(Reagent::White) => Rgb::new(1.0, 1.0, 1.0),
509 Some(Reagent::Yellow) => Rgb::new(1.0, 1.0, 0.0),
510 Some(Reagent::FireRain) => Rgb::new(1.0, 0.8, 0.3),
511 Some(Reagent::FireGigas) => Rgb::new(1.0, 0.6, 0.2),
512 None => Rgb::new(1.0, 0.5, 0.0),
513 },
514 power
515 * if *is_attack || reagent.is_none() {
516 2.5
517 } else {
518 5.0
519 },
520 ),
521 timeout: match reagent {
522 Some(_) => 1.0,
523 None => 0.5,
524 },
525 fadeout: |timeout| timeout * 2.0,
526 }),
527 Outcome::ProjectileShot { .. } => {},
528 _ => {},
529 }
530 }
531
532 pub fn maintain(
535 &mut self,
536 renderer: &mut Renderer,
537 audio: &mut AudioFrontend,
538 scene_data: &SceneData,
539 client: &Client,
540 settings: &Settings,
541 ) {
542 span!(_guard, "maintain", "Scene::maintain");
543 let ecs = scene_data.state.ecs();
545
546 let dt = ecs.fetch::<DeltaTime>().0;
547
548 self.local_time += dt as f64 * ecs.fetch::<TimeScale>().0;
549
550 let positions = ecs.read_storage::<comp::Pos>();
551
552 let viewpoint_pos = if let Some(viewpoint_pos) =
553 positions.get(scene_data.viewpoint_entity).map(|pos| pos.0)
554 {
555 let viewpoint_ori = ecs
556 .read_storage::<comp::Ori>()
557 .get(scene_data.viewpoint_entity)
558 .map_or(Quaternion::identity(), |ori| ori.to_quat());
559
560 let viewpoint_look_ori = ecs
561 .read_storage::<comp::CharacterActivity>()
562 .get(scene_data.viewpoint_entity)
563 .and_then(|activity| activity.look_dir)
564 .map(|dir| {
565 let d = dir.to_vec();
566
567 let pitch = (-d.z).asin();
568 let yaw = d.x.atan2(d.y);
569
570 Vec3::new(yaw, pitch, 0.0)
571 })
572 .unwrap_or_else(|| {
573 let q = viewpoint_ori;
574 let sinr_cosp = 2.0 * (q.w * q.x + q.y * q.z);
575 let cosr_cosp = 1.0 - 2.0 * (q.x * q.x + q.y * q.y);
576 let pitch = sinr_cosp.atan2(cosr_cosp);
577
578 let siny_cosp = 2.0 * (q.w * q.z + q.x * q.y);
579 let cosy_cosp = 1.0 - 2.0 * (q.y * q.y + q.z * q.z);
580 let yaw = siny_cosp.atan2(cosy_cosp);
581
582 Vec3::new(-yaw, -pitch, 0.0)
583 });
584
585 let viewpoint_scale = ecs
586 .read_storage::<comp::Scale>()
587 .get(scene_data.viewpoint_entity)
588 .map_or(1.0, |scale| scale.0);
589
590 let (is_humanoid, viewpoint_height, viewpoint_eye_height) = ecs
591 .read_storage::<comp::Body>()
592 .get(scene_data.viewpoint_entity)
593 .map_or((false, 1.0, 0.0), |b| {
594 (
595 matches!(b, comp::Body::Humanoid(_)),
596 b.height(),
597 b.eye_height(1.0), )
599 });
600
601 if scene_data.mutable_viewpoint || matches!(self.camera.get_mode(), CameraMode::Freefly)
602 {
603 self.camera
605 .rotate_by(Vec3::from([self.camera_input_state.x, 0.0, 0.0]));
606 self.camera
607 .rotate_by(Vec3::from([0.0, self.camera_input_state.y, 0.0]));
608 } else {
609 self.camera.set_orientation(viewpoint_look_ori);
611 }
612
613 let viewpoint_offset = if is_humanoid {
614 let viewpoint_rolling = ecs
615 .read_storage::<comp::CharacterState>()
616 .get(scene_data.viewpoint_entity)
617 .is_some_and(|cs| cs.is_dodge());
618
619 let is_running = ecs
620 .read_storage::<comp::Vel>()
621 .get(scene_data.viewpoint_entity)
622 .zip(
623 ecs.read_storage::<comp::PhysicsState>()
624 .get(scene_data.viewpoint_entity),
625 )
626 .map(|(v, ps)| {
627 (v.0 - ps.ground_vel).magnitude_squared() > RUNNING_THRESHOLD.powi(2)
628 })
629 .unwrap_or(false);
630
631 let on_ground = ecs
632 .read_storage::<comp::PhysicsState>()
633 .get(scene_data.viewpoint_entity)
634 .map(|p| p.on_ground.is_some());
635
636 let player_entity = client.entity();
637 let holding_ranged = client
638 .inventories()
639 .get(player_entity)
640 .and_then(|inv| inv.equipped(EquipSlot::ActiveMainhand))
641 .and_then(|item| item.tool_info())
642 .is_some_and(|tool_kind| {
643 matches!(
644 tool_kind,
645 ToolKind::Bow
646 | ToolKind::Staff
647 | ToolKind::Sceptre
648 | ToolKind::Throwable
649 )
650 })
651 || client
652 .current::<CharacterState>()
653 .is_some_and(|char_state| matches!(char_state, CharacterState::Throw(_)));
654
655 let up = match self.camera.get_mode() {
656 CameraMode::FirstPerson => {
657 if viewpoint_rolling {
658 viewpoint_height * 0.42
659 } else if is_running && on_ground.unwrap_or(false) {
660 viewpoint_eye_height
661 + (scene_data.state.get_time() as f32 * 17.0).sin() * 0.05
662 } else {
663 viewpoint_eye_height
664 }
665 },
666 CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => {
667 viewpoint_height * 1.16 + settings.gameplay.aim_offset_y
668 },
669 CameraMode::ThirdPerson if scene_data.is_aiming => viewpoint_height * 1.16,
670 CameraMode::ThirdPerson => viewpoint_eye_height,
671 CameraMode::Freefly => 0.0,
672 };
673
674 let right = match self.camera.get_mode() {
675 CameraMode::FirstPerson => 0.0,
676 CameraMode::ThirdPerson if scene_data.is_aiming && holding_ranged => {
677 settings.gameplay.aim_offset_x
678 },
679 CameraMode::ThirdPerson => 0.0,
680 CameraMode::Freefly => 0.0,
681 };
682
683 let tilt = self.camera.get_orientation().y;
685 let dist = self.camera.get_distance();
686
687 Vec3::unit_z() * (up * viewpoint_scale - tilt.min(0.0).sin() * dist * 0.6)
688 + self.camera.right() * (right * viewpoint_scale)
689 } else {
690 self.figure_mgr
691 .viewpoint_offset(scene_data, scene_data.viewpoint_entity)
692 };
693
694 match self.camera.get_mode() {
695 CameraMode::FirstPerson | CameraMode::ThirdPerson => {
696 self.camera.set_focus_pos(viewpoint_pos + viewpoint_offset);
697 },
698 CameraMode::Freefly => {},
699 };
700
701 self.camera
703 .update(scene_data.state.get_time(), dt, scene_data.mouse_smoothing);
704 viewpoint_pos
705 } else {
706 Vec3::zero()
707 };
708
709 self.camera.compute_dependents(&scene_data.state.terrain());
711 let camera::Dependents {
712 view_mat,
713 view_mat_inv,
714 proj_mat,
715 proj_mat_inv,
716 cam_pos,
717 ..
718 } = self.camera.dependents();
719
720 let loaded_distance =
722 (0.98 * self.loaded_distance + 0.02 * scene_data.loaded_distance).max(0.01);
723
724 let lights = &mut self.light_data;
726 lights.clear();
727
728 self.particle_mgr.maintain(
730 renderer,
731 scene_data,
732 &self.terrain,
733 &self.figure_mgr,
734 lights,
735 );
736
737 self.trail_mgr.maintain(renderer, scene_data);
739
740 let max_light_dist = loaded_distance.powi(2) + LIGHT_DIST_RADIUS;
742 lights.extend(
743 (
744 &scene_data.state.ecs().read_storage::<comp::Pos>(),
745 scene_data
746 .state
747 .ecs()
748 .read_storage::<crate::ecs::comp::Interpolated>()
749 .maybe(),
750 &scene_data
751 .state
752 .ecs()
753 .read_storage::<comp::LightAnimation>(),
754 scene_data
755 .state
756 .ecs()
757 .read_storage::<comp::Health>()
758 .maybe(),
759 )
760 .join()
761 .filter(|(pos, _, light_anim, h)| {
762 light_anim.col != Rgb::zero()
763 && light_anim.strength > 0.0
764 && pos.0.distance_squared(viewpoint_pos) < max_light_dist
765 && h.is_none_or(|h| !h.is_dead)
766 })
767 .map(|(pos, interpolated, light_anim, _)| {
768 let pos = interpolated.map_or(pos.0, |i| i.pos);
770 Light::new(pos + light_anim.offset, light_anim.col, light_anim.strength)
771 })
772 .chain(
773 self.event_lights
774 .iter()
775 .map(|el| el.light.with_strength((el.fadeout)(el.timeout))),
776 ),
777 );
778 let voxel_colliders_manifest = VOXEL_COLLIDER_MANIFEST.read();
779 let figure_mgr = &self.figure_mgr;
780 lights.extend(
781 (
782 &scene_data.state.ecs().entities(),
783 &scene_data
784 .state
785 .read_storage::<crate::ecs::comp::Interpolated>(),
786 &scene_data.state.read_storage::<comp::Body>(),
787 &scene_data.state.read_storage::<comp::Collider>(),
788 )
789 .join()
790 .filter_map(|(entity, interpolated, body, collider)| {
791 let vol = collider.get_vol(&voxel_colliders_manifest)?;
792 let (blocks_of_interest, offset) =
793 figure_mgr.get_blocks_of_interest(entity, body, Some(collider))?;
794
795 let mat = Mat4::from(interpolated.ori.to_quat())
796 .translated_3d(interpolated.pos)
797 * Mat4::translation_3d(offset);
798
799 let p = mat.inverted().mul_point(viewpoint_pos);
800 let aabb = Aabb {
801 min: Vec3::zero(),
802 max: vol.volume().sz.as_(),
803 };
804 if aabb.contains_point(p) || aabb.distance_to_point(p) < max_light_dist {
805 Some(
806 blocks_of_interest
807 .lights
808 .iter()
809 .map(move |(block_offset, level)| {
810 let wpos = mat.mul_point(block_offset.as_() + 0.5);
811 (wpos, level)
812 })
813 .filter(move |(wpos, _)| {
814 wpos.distance_squared(viewpoint_pos) < max_light_dist
815 })
816 .map(|(wpos, level)| {
817 Light::new(wpos, Rgb::white(), *level as f32 / 7.0)
818 }),
819 )
820 } else {
821 None
822 }
823 })
824 .flatten(),
825 );
826 lights.sort_by_key(|light| light.get_pos().distance_squared(viewpoint_pos) as i32);
827 lights.truncate(MAX_LIGHT_COUNT);
828 renderer.update_consts(&mut self.data.lights, lights);
829
830 self.event_lights.retain_mut(|el| {
832 el.timeout -= dt;
833 el.timeout > 0.0
834 });
835
836 let mut shadows = (
838 &scene_data.state.ecs().read_storage::<comp::Pos>(),
839 scene_data
840 .state
841 .ecs()
842 .read_storage::<crate::ecs::comp::Interpolated>()
843 .maybe(),
844 scene_data.state.ecs().read_storage::<comp::Scale>().maybe(),
845 &scene_data.state.ecs().read_storage::<comp::Body>(),
846 &scene_data.state.ecs().read_storage::<comp::Health>(),
847 )
848 .join()
849 .filter(|(_, _, _, _, health)| !health.is_dead)
850 .filter(|(pos, _, _, _, _)| {
851 pos.0.distance_squared(viewpoint_pos)
852 < (loaded_distance.min(SHADOW_MAX_DIST) + SHADOW_DIST_RADIUS).powi(2)
853 })
854 .map(|(pos, interpolated, scale, _, _)| {
855 Shadow::new(
856 interpolated.map_or(pos.0, |i| i.pos),
858 scale.map_or(1.0, |s| s.0),
859 )
860 })
861 .collect::<Vec<_>>();
862 shadows.sort_by_key(|shadow| shadow.get_pos().distance_squared(viewpoint_pos) as i32);
863 shadows.truncate(MAX_SHADOW_COUNT);
864 renderer.update_consts(&mut self.data.shadows, &shadows);
865
866 self.loaded_distance = loaded_distance;
868
869 const DAY: f64 = 60.0 * 60.0 * 24.0;
877 let time_of_day = scene_data.state.get_time_of_day();
878 let max_lerp_period = if scene_data.flashing_lights_enabled {
879 DAY * 2.0
880 } else {
881 DAY * 0.25
882 };
883 self.interpolated_time_of_day =
884 Some(self.interpolated_time_of_day.map_or(time_of_day, |tod| {
885 if (tod - time_of_day).abs() > max_lerp_period {
886 time_of_day
887 } else {
888 Lerp::lerp(tod, time_of_day, dt as f64)
889 }
890 }));
891 let time_of_day = self.interpolated_time_of_day.unwrap_or(time_of_day);
892 let focus_pos = self.camera.get_focus_pos();
893 let focus_off = focus_pos.map(|e| e.trunc());
894
895 renderer.update_consts(&mut self.data.globals, &[Globals::new(
897 view_mat,
898 proj_mat,
899 cam_pos,
900 focus_pos,
901 self.loaded_distance,
902 self.lod.get_data().tgt_detail as f32,
903 self.map_bounds,
904 time_of_day,
905 scene_data.state.get_time(),
906 self.local_time,
907 renderer.resolution().as_(),
908 Vec2::new(SHADOW_NEAR, SHADOW_FAR),
909 lights.len(),
910 shadows.len(),
911 NUM_DIRECTED_LIGHTS,
912 scene_data
913 .state
914 .terrain()
915 .get((cam_pos + focus_off).map(|e| e.floor() as i32))
916 .ok()
917 .filter(|b| !(b.is_filled() && client.is_moderator()))
919 .map(|b| b.kind())
920 .unwrap_or(BlockKind::Air),
921 self.select_pos.map(|e| e - focus_off.map(|e| e as i32)),
922 scene_data.gamma,
923 scene_data.exposure,
924 self.last_lightning.unwrap_or((Vec3::zero(), -1000.0)),
925 self.wind_vel,
926 scene_data.ambiance,
927 self.camera.get_mode(),
928 scene_data.sprite_render_distance - 20.0,
929 )]);
930 renderer.update_clouds_locals(CloudsLocals::new(proj_mat_inv, view_mat_inv));
931 renderer.update_postprocess_locals(PostProcessLocals::new(proj_mat_inv, view_mat_inv));
932
933 self.lod.maintain(renderer, client, focus_pos, &self.camera);
935
936 self.tether_mgr.maintain(renderer, client, focus_pos);
938
939 self.debug.maintain(renderer);
941
942 let (
944 _visible_bounds,
945 visible_light_volume,
946 visible_psr_bounds,
947 visible_occlusion_volume,
948 visible_por_bounds,
949 ) = self.terrain.maintain(
950 renderer,
951 scene_data,
952 focus_pos,
953 self.loaded_distance,
954 &self.camera,
955 );
956
957 let _figure_bounds = self.figure_mgr.maintain(
959 renderer,
960 &mut self.trail_mgr,
961 scene_data,
962 visible_psr_bounds,
963 visible_por_bounds,
964 &self.camera,
965 Some(&self.terrain),
966 );
967
968 let fov = self.camera.get_effective_fov();
969 let aspect_ratio = self.camera.get_aspect_ratio();
970 let view_dir = ((focus_pos.map(f32::fract)) - cam_pos).normalized();
971
972 let look_at = cam_pos;
977 let new_dir = view_dir;
978 let new_dir = new_dir.normalized();
979 let up: math::Vec3<f32> = math::Vec3::unit_y();
980
981 let texture_mat = Mat4::<f32>::scaling_3d::<Vec3<f32>>(Vec3::new(0.5, -0.5, 1.0))
995 * Mat4::translation_3d(Vec3::new(1.0, -1.0, 0.0));
996
997 let directed_mats = |d_view_mat: math::Mat4<f32>,
998 d_dir: math::Vec3<f32>,
999 volume: &Vec<math::Vec3<f32>>|
1000 -> (Mat4<f32>, Mat4<f32>) {
1001 let v_p_orig = math::Vec3::from(d_view_mat * math::Vec4::from_direction(new_dir));
1003 let mut v_p = v_p_orig.normalized();
1004 let cos_gamma = new_dir.map(f64::from).dot(d_dir.map(f64::from));
1005 let sin_gamma = (1.0 - cos_gamma * cos_gamma).sqrt();
1006 let gamma = sin_gamma.asin();
1007 let view_mat = math::Mat4::from_col_array(view_mat.into_col_array());
1008 let bounds1 = math::fit_psr(
1011 view_mat.map_cols(math::Vec4::from),
1012 volume.iter().copied(),
1013 math::Vec4::homogenized,
1014 );
1015 let n_e = f64::from(-bounds1.max.z);
1016 let factor = compute_warping_parameter_perspective(
1017 gamma,
1018 n_e,
1019 f64::from(fov),
1020 f64::from(aspect_ratio),
1021 );
1022
1023 v_p.z = 0.0;
1024 v_p.normalize();
1025 let l_r: math::Mat4<f32> = if factor > EPSILON_UPSILON {
1026 math::Mat4::look_at_lh(math::Vec3::zero(), math::Vec3::unit_z(), v_p)
1030 } else {
1031 math::Mat4::identity()
1032 };
1033 let directed_proj_mat = math::Mat4::new(
1035 1.0, 0.0, 0.0, 0.0, 0.0, 1.0, 0.0, 0.0, 0.0, 0.0, -1.0, 0.0, 0.0, 0.0, 0.0, 1.0,
1036 );
1037
1038 let light_all_mat = l_r * directed_proj_mat * d_view_mat;
1039 let bounds0 = math::fit_psr(
1042 light_all_mat,
1043 volume.iter().copied(),
1044 math::Vec4::homogenized,
1045 );
1046 let (z_0, z_1) = {
1054 let f_e = f64::from(-bounds1.min.z).max(n_e);
1055 let p_z = bounds1.max.z;
1057 let p_y = bounds0.min.y;
1059 let p_x = bounds0.center().x;
1060 let view_inv = view_mat.inverted();
1062 let light_all_inv = light_all_mat.inverted();
1064
1065 let view_point = view_inv
1067 * math::Vec4::from_point(
1068 -math::Vec3::unit_z() * p_z, );
1070 let view_plane = view_mat.transposed() * -math::Vec4::unit_z();
1071
1072 let light_point = light_all_inv
1074 * math::Vec4::from_point(
1075 math::Vec3::unit_y() * p_y, );
1077 let light_plane = light_all_mat.transposed() * math::Vec4::unit_y();
1078
1079 let shadow_point = light_all_inv
1081 * math::Vec4::from_point(
1082 math::Vec3::unit_x() * p_x, );
1084 let shadow_plane = light_all_mat.transposed() * math::Vec4::unit_x();
1085
1086 let solve_p0 = math::Mat4::new(
1090 view_plane.x,
1091 view_plane.y,
1092 view_plane.z,
1093 0.0,
1094 light_plane.x,
1095 light_plane.y,
1096 light_plane.z,
1097 0.0,
1098 shadow_plane.x,
1099 shadow_plane.y,
1100 shadow_plane.z,
1101 0.0,
1102 0.0,
1103 0.0,
1104 0.0,
1105 1.0,
1106 );
1107
1108 let plane_dist = math::Vec4::new(
1110 view_plane.dot(view_point),
1111 light_plane.dot(light_point),
1112 shadow_plane.dot(shadow_point),
1113 1.0,
1114 );
1115 let p0_world = solve_p0.inverted() * plane_dist;
1116 let p0 = light_all_mat * p0_world;
1118 let mut p1 = p0;
1119 p1.y = bounds0.max.y;
1121
1122 let view_from_light_mat = view_mat * light_all_inv;
1125 let z0 = view_from_light_mat * p0;
1127 let z1 = view_from_light_mat * p1;
1128
1129 (
1134 f64::from(z0.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
1135 f64::from(z1.homogenized().dot(-math::Vec4::unit_z())).clamp(n_e, f_e),
1136 )
1137 };
1138
1139 let mut light_focus_pos: math::Vec3<f32> = math::Vec3::zero();
1141 light_focus_pos.x = bounds0.center().x;
1142 light_focus_pos.y = bounds0.min.y;
1143 light_focus_pos.z = bounds0.center().z;
1144
1145 let d = f64::from(bounds0.max.y - bounds0.min.y).abs();
1146
1147 let w_l_y = d;
1148
1149 let alpha = z_1 / z_0;
1153 let alpha_sqrt = alpha.sqrt();
1154 let directed_near_normal = if factor < 0.0 {
1155 (1.0 + alpha_sqrt - factor * (alpha - 1.0)) / ((alpha - 1.0) * (factor + 1.0))
1157 } else {
1158 ((alpha_sqrt - 1.0) * (factor * alpha_sqrt + 1.0)).recip()
1160 };
1161
1162 let y_ = |v: f64| w_l_y * (v + directed_near_normal).abs();
1164 let directed_near = y_(0.0) as f32;
1165 let directed_far = y_(1.0) as f32;
1166 light_focus_pos.y = if factor > EPSILON_UPSILON {
1167 light_focus_pos.y - directed_near
1168 } else {
1169 light_focus_pos.y
1170 };
1171 let w_v: math::Mat4<f32> = math::Mat4::translation_3d(-math::Vec3::new(
1173 light_focus_pos.x,
1174 light_focus_pos.y,
1175 light_focus_pos.z,
1176 ));
1177 let shadow_view_mat: math::Mat4<f32> = w_v * light_all_mat;
1178 let w_p: math::Mat4<f32> = {
1179 if factor > EPSILON_UPSILON {
1180 let near = directed_near;
1182 let far = directed_far;
1183 let left = -1.0;
1184 let right = 1.0;
1185 let bottom = -1.0;
1186 let top = 1.0;
1187 let s_x = 2.0 * near / (right - left);
1188 let o_x = (right + left) / (right - left);
1189 let s_z = 2.0 * near / (top - bottom);
1190 let o_z = (top + bottom) / (top - bottom);
1191
1192 let s_y = (far + near) / (far - near);
1193 let o_y = -2.0 * far * near / (far - near);
1194
1195 math::Mat4::new(
1196 s_x, o_x, 0.0, 0.0, 0.0, s_y, 0.0, o_y, 0.0, o_z, s_z, 0.0, 0.0, 1.0, 0.0,
1197 0.0,
1198 )
1199 } else {
1200 math::Mat4::identity()
1201 }
1202 };
1203
1204 let shadow_all_mat: math::Mat4<f32> = w_p * shadow_view_mat;
1205 let math::Aabb::<f32> {
1208 min:
1209 math::Vec3 {
1210 x: xmin,
1211 y: ymin,
1212 z: zmin,
1213 },
1214 max:
1215 math::Vec3 {
1216 x: xmax,
1217 y: ymax,
1218 z: zmax,
1219 },
1220 } = math::fit_psr(
1221 shadow_all_mat,
1222 volume.iter().copied(),
1223 math::Vec4::homogenized,
1224 );
1225 let s_x = 2.0 / (xmax - xmin);
1226 let s_y = 2.0 / (ymax - ymin);
1227 let s_z = 1.0 / (zmax - zmin);
1228 let o_x = -(xmax + xmin) / (xmax - xmin);
1229 let o_y = -(ymax + ymin) / (ymax - ymin);
1230 let o_z = -zmin / (zmax - zmin);
1231 let directed_proj_mat = Mat4::new(
1232 s_x, 0.0, 0.0, o_x, 0.0, s_y, 0.0, o_y, 0.0, 0.0, s_z, o_z, 0.0, 0.0, 0.0, 1.0,
1233 );
1234
1235 let shadow_all_mat: Mat4<f32> = Mat4::from_col_arrays(shadow_all_mat.into_col_arrays());
1236
1237 let directed_texture_proj_mat = texture_mat * directed_proj_mat;
1238 (
1239 directed_proj_mat * shadow_all_mat,
1240 directed_texture_proj_mat * shadow_all_mat,
1241 )
1242 };
1243
1244 let weather = client
1245 .state()
1246 .max_weather_near(focus_off.xy() + cam_pos.xy());
1247 self.wind_vel = weather.wind_vel();
1248 if weather.rain > RAIN_THRESHOLD {
1249 let weather = client.weather_at_player();
1250 let rain_vel = weather.rain_vel();
1251 let rain_view_mat = math::Mat4::look_at_rh(look_at, look_at + rain_vel, up);
1252
1253 self.integrated_rain_vel += rain_vel.magnitude() * dt;
1254 let rain_dir_mat = Mat4::rotation_from_to_3d(-Vec3::unit_z(), rain_vel);
1255
1256 let (shadow_mat, texture_mat) =
1257 directed_mats(rain_view_mat, rain_vel, &visible_occlusion_volume);
1258
1259 let rain_occlusion_locals = RainOcclusionLocals::new(
1260 shadow_mat,
1261 texture_mat,
1262 rain_dir_mat,
1263 weather.rain,
1264 self.integrated_rain_vel,
1265 );
1266
1267 renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
1268 } else if self.integrated_rain_vel > 0.0 {
1269 self.integrated_rain_vel = 0.0;
1270 let rain_occlusion_locals = RainOcclusionLocals::default();
1272 renderer.update_consts(&mut self.data.rain_occlusion_mats, &[rain_occlusion_locals]);
1273 }
1274
1275 let sun_dir = scene_data.get_sun_dir();
1276 let is_daylight = sun_dir.z < 0.0;
1277 if renderer.pipeline_modes().shadow.is_map() && (is_daylight || !lights.is_empty()) {
1278 let (point_shadow_res, _directed_shadow_res) = renderer.get_shadow_resolution();
1279 let point_shadow_aspect = point_shadow_res.x as f32 / point_shadow_res.y as f32;
1282 let directed_light_dir = sun_dir;
1285
1286 let mut directed_shadow_mats = Vec::with_capacity(6);
1289
1290 let light_view_mat = math::Mat4::look_at_rh(look_at, look_at + directed_light_dir, up);
1291 let (shadow_mat, texture_mat) =
1292 directed_mats(light_view_mat, directed_light_dir, &visible_light_volume);
1293
1294 let shadow_locals = ShadowLocals::new(shadow_mat, texture_mat);
1295
1296 renderer.update_consts(&mut self.data.shadow_mats, &[shadow_locals]);
1297
1298 directed_shadow_mats.push(light_view_mat);
1299 directed_shadow_mats
1301 .extend_from_slice(&[math::Mat4::default(); 6 - NUM_DIRECTED_LIGHTS] as _);
1302 let mut shadow_mats = Vec::with_capacity(6 * (lights.len() + 1));
1305 shadow_mats.resize_with(6, PointLightMatrix::default);
1306 let shadow_proj = camera::perspective_rh_zo_general(
1312 90.0f32.to_radians(),
1313 point_shadow_aspect,
1314 1.0 / SHADOW_NEAR,
1315 1.0 / SHADOW_FAR,
1316 );
1317 let shadow_proj = shadow_proj * Mat4::scaling_3d(-1.0);
1322
1323 let orientations = [
1326 (Vec3::new(1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
1327 (Vec3::new(-1.0, 0.0, 0.0), Vec3::new(0.0, -1.0, 0.0)),
1328 (Vec3::new(0.0, 1.0, 0.0), Vec3::new(0.0, 0.0, 1.0)),
1329 (Vec3::new(0.0, -1.0, 0.0), Vec3::new(0.0, 0.0, -1.0)),
1330 (Vec3::new(0.0, 0.0, 1.0), Vec3::new(0.0, -1.0, 0.0)),
1331 (Vec3::new(0.0, 0.0, -1.0), Vec3::new(0.0, -1.0, 0.0)),
1332 ];
1333
1334 shadow_mats.extend(lights.iter().flat_map(|light| {
1338 let eye = Vec3::new(light.pos[0], light.pos[1], light.pos[2]) - focus_off;
1341 orientations.iter().map(move |&(forward, up)| {
1342 PointLightMatrix::new(shadow_proj * Mat4::look_at_lh(eye, eye + forward, up))
1345 })
1346 }));
1347
1348 for (i, val) in shadow_mats.into_iter().enumerate() {
1349 self.data.point_light_matrices[i] = val
1350 }
1351 }
1352
1353 self.figure_mgr.clean(scene_data.tick);
1355
1356 self.sfx_mgr.maintain(
1358 audio,
1359 scene_data.state,
1360 scene_data.viewpoint_entity,
1361 &self.camera,
1362 &self.terrain,
1363 client,
1364 );
1365
1366 self.ambience_mgr.maintain(
1367 audio,
1368 &settings.audio,
1369 scene_data.state,
1370 client,
1371 &self.camera,
1372 );
1373
1374 self.music_mgr.maintain(audio, scene_data.state, client);
1375 }
1376
1377 pub fn global_bind_group(&self) -> &GlobalsBindGroup { &self.globals_bind_group }
1378
1379 pub fn render(
1381 &self,
1382 drawer: &mut Drawer<'_>,
1383 state: &State,
1384 viewpoint_entity: EcsEntity,
1385 tick: u64,
1386 scene_data: &SceneData,
1387 ) {
1388 span!(_guard, "render", "Scene::render");
1389 let sun_dir = scene_data.get_sun_dir();
1390 let is_daylight = sun_dir.z < 0.0;
1391 let focus_pos = self.camera.get_focus_pos();
1392 let cam_pos = self.camera.dependents().cam_pos + focus_pos.map(|e| e.trunc());
1393 let is_rain = state.max_weather_near(cam_pos.xy()).rain > RAIN_THRESHOLD;
1394 let culling_mode = if scene_data
1395 .state
1396 .terrain()
1397 .get_key(scene_data.state.terrain().pos_key(cam_pos.as_()))
1398 .is_some_and(|c| cam_pos.z < c.meta().alt() - terrain::UNDERGROUND_ALT)
1399 {
1400 CullingMode::Underground
1401 } else {
1402 CullingMode::Surface
1403 };
1404
1405 let camera_data = (&self.camera, scene_data.figure_lod_render_distance);
1406
1407 if drawer.pipeline_modes().shadow.is_map() && (is_daylight || !self.light_data.is_empty()) {
1409 if is_daylight {
1410 prof_span!("directed shadows");
1411 if let Some(mut shadow_pass) = drawer.shadow_pass() {
1412 self.terrain.render_shadows(
1414 &mut shadow_pass.draw_terrain_shadows(),
1415 focus_pos,
1416 culling_mode,
1417 );
1418
1419 self.figure_mgr.render_shadows(
1421 &mut shadow_pass.draw_figure_shadows(),
1422 state,
1423 tick,
1424 camera_data,
1425 );
1426 self.debug
1427 .render_shadows(&mut shadow_pass.draw_debug_shadows());
1428 }
1429 }
1430
1431 {
1433 prof_span!("point shadows");
1434 drawer.draw_point_shadows(
1435 &self.data.point_light_matrices,
1436 self.terrain.chunks_for_point_shadows(focus_pos),
1437 )
1438 }
1439 }
1440 if is_rain {
1442 prof_span!("rain occlusion");
1443 if let Some(mut occlusion_pass) = drawer.rain_occlusion_pass() {
1444 self.terrain
1445 .render_rain_occlusion(&mut occlusion_pass.draw_terrain_shadows(), cam_pos);
1446
1447 self.figure_mgr.render_rain_occlusion(
1448 &mut occlusion_pass.draw_figure_shadows(),
1449 state,
1450 tick,
1451 camera_data,
1452 );
1453 }
1454 }
1455
1456 prof_span!(guard, "main pass");
1457 if let Some(mut first_pass) = drawer.first_pass() {
1458 self.figure_mgr.render_viewpoint(
1459 &mut first_pass.draw_figures(),
1460 state,
1461 viewpoint_entity,
1462 tick,
1463 camera_data,
1464 );
1465
1466 self.terrain
1467 .render(&mut first_pass, focus_pos, culling_mode);
1468
1469 self.figure_mgr.render(
1470 &mut first_pass.draw_figures(),
1471 state,
1472 viewpoint_entity,
1473 tick,
1474 camera_data,
1475 );
1476
1477 self.lod.render(&mut first_pass, culling_mode);
1478
1479 first_pass.draw_skybox(&self.skybox.model);
1481
1482 let mut sprite_drawer = first_pass.draw_sprites(
1484 &self.terrain.sprite_globals,
1485 &self.terrain.sprite_render_state.sprite_atlas_textures,
1486 );
1487 self.figure_mgr.render_sprites(
1488 &mut sprite_drawer,
1489 state,
1490 cam_pos,
1491 scene_data.sprite_render_distance,
1492 );
1493 self.terrain.render_sprites(
1494 &mut sprite_drawer,
1495 focus_pos,
1496 cam_pos,
1497 scene_data.sprite_render_distance,
1498 culling_mode,
1499 );
1500 drop(sprite_drawer);
1501
1502 self.tether_mgr.render(&mut first_pass);
1504
1505 self.terrain.render_translucent(&mut first_pass, focus_pos);
1507
1508 self.particle_mgr
1510 .render(&mut first_pass.draw_particles(), scene_data);
1511
1512 self.debug.render(&mut first_pass.draw_debug());
1514 }
1515 drop(guard);
1516 }
1517
1518 pub fn maintain_debug_hitboxes(
1519 &mut self,
1520 client: &Client,
1521 settings: &Settings,
1522 hitboxes: &mut HashMap<specs::Entity, DebugShapeId>,
1523 tracks: &mut HashMap<Vec2<i32>, Vec<DebugShapeId>>,
1524 gizmos: &mut Vec<(DebugShapeId, common::resources::Time, bool)>,
1525 ) {
1526 let ecs = client.state().ecs();
1527 {
1528 let mut current_chunks = hashbrown::HashSet::new();
1529 let terrain_grid = ecs.read_resource::<TerrainGrid>();
1530 for (key, chunk) in terrain_grid.iter() {
1531 current_chunks.insert(key);
1532 tracks.entry(key).or_insert_with(|| {
1533 let mut ret = Vec::new();
1534 for bezier in chunk.meta().tracks().iter() {
1535 let shape_id = self.debug.add_shape(DebugShape::TrainTrack {
1536 path: *bezier,
1537 rail_width: 0.25,
1538 rail_sep: 1.0,
1539 plank_width: 0.5,
1540 plank_height: 0.125,
1541 plank_sep: 2.0,
1542 });
1543 ret.push(shape_id);
1544 self.debug
1545 .set_context(shape_id, [0.0; 4], [1.0; 4], [0.0, 0.0, 0.0, 1.0]);
1546 }
1547 for point in chunk.meta().debug_points().iter() {
1548 let shape_id = self.debug.add_shape(DebugShape::Cylinder {
1549 radius: 0.1,
1550 height: 0.1,
1551 });
1552 ret.push(shape_id);
1553 self.debug.set_context(
1554 shape_id,
1555 point.with_w(0.0).into_array(),
1556 [1.0; 4],
1557 [0.0, 0.0, 0.0, 1.0],
1558 );
1559 }
1560 for line in chunk.meta().debug_lines().iter() {
1561 let shape_id = self
1562 .debug
1563 .add_shape(DebugShape::Line([line.start, line.end], 0.1));
1564 ret.push(shape_id);
1565 self.debug
1566 .set_context(shape_id, [0.0; 4], [1.0; 4], [0.0, 0.0, 0.0, 1.0]);
1567 }
1568 ret
1569 });
1570 }
1571 tracks.retain(|k, v| {
1572 let keep = current_chunks.contains(k);
1573 if !keep {
1574 for shape in v.iter() {
1575 self.debug.remove_shape(*shape);
1576 }
1577 }
1578 keep
1579 });
1580 }
1581 let mut current_entities = hashbrown::HashSet::new();
1582 if settings.interface.toggle_hitboxes {
1583 let positions = ecs.read_component::<comp::Pos>();
1584 let colliders = ecs.read_component::<comp::Collider>();
1585 let orientations = ecs.read_component::<comp::Ori>();
1586 let scales = ecs.read_component::<comp::Scale>();
1587 let groups = ecs.read_component::<comp::Group>();
1588 for (entity, pos, collider, ori, scale, group) in (
1589 &ecs.entities(),
1590 &positions,
1591 &colliders,
1592 &orientations,
1593 scales.maybe(),
1594 groups.maybe(),
1595 )
1596 .join()
1597 {
1598 match collider {
1599 comp::Collider::CapsulePrism {
1600 p0,
1601 p1,
1602 radius,
1603 z_min,
1604 z_max,
1605 } => {
1606 let scale = scale.map_or(1.0, |s| s.0);
1607 current_entities.insert(entity);
1608
1609 let shape = DebugShape::CapsulePrism {
1610 p0: *p0 * scale,
1611 p1: *p1 * scale,
1612 radius: *radius * scale,
1613 height: (*z_max - *z_min) * scale,
1614 };
1615
1616 if let Some(shape_id) = hitboxes.get(&entity) {
1618 if self.debug.get_shape(*shape_id).is_some_and(|s| s != &shape) {
1619 self.debug.remove_shape(*shape_id);
1620 hitboxes.remove(&entity);
1621 }
1622 }
1623
1624 let shape_id = hitboxes
1625 .entry(entity)
1626 .or_insert_with(|| self.debug.add_shape(shape));
1627 let hb_pos = [pos.0.x, pos.0.y, pos.0.z + *z_min * scale, 0.0];
1628 let color = if group == Some(&comp::group::ENEMY) {
1629 [1.0, 0.0, 0.0, 0.5]
1630 } else if group == Some(&comp::group::NPC) {
1631 [0.0, 0.0, 1.0, 0.5]
1632 } else {
1633 [0.0, 1.0, 0.0, 0.5]
1634 };
1635 let ori = ori.to_quat();
1637 let hb_ori = [ori.x, ori.y, ori.z, ori.w];
1638 self.debug.set_context(*shape_id, hb_pos, color, hb_ori);
1639 },
1640 comp::Collider::Voxel { .. }
1641 | comp::Collider::Volume(_)
1642 | comp::Collider::Point => {
1643 },
1645 }
1646 }
1647 }
1648 hitboxes.retain(|k, v| {
1649 let keep = current_entities.contains(k);
1650 if !keep {
1651 self.debug.remove_shape(*v);
1652 }
1653 keep
1654 });
1655
1656 let time = client.state().get_time();
1657 gizmos.retain(|(id, end_time, _)| {
1658 let keep = end_time.0 > time;
1659 if !keep {
1660 self.debug.remove_shape(*id);
1661 }
1662 keep
1663 });
1664 }
1665
1666 pub fn maintain_debug_vectors(&mut self, client: &Client, lines: &mut PlayerDebugLines) {
1667 lines
1668 .chunk_normal
1669 .take()
1670 .map(|id| self.debug.remove_shape(id));
1671 lines.fluid_vel.take().map(|id| self.debug.remove_shape(id));
1672 lines.wind.take().map(|id| self.debug.remove_shape(id));
1673 lines.vel.take().map(|id| self.debug.remove_shape(id));
1674 if self.debug_vectors_enabled {
1675 let ecs = client.state().ecs();
1676
1677 let vels = &ecs.read_component::<comp::Vel>();
1678 let Some(vel) = vels.get(client.entity()) else {
1679 return;
1680 };
1681
1682 let phys_states = &ecs.read_component::<comp::PhysicsState>();
1683 let Some(phys) = phys_states.get(client.entity()) else {
1684 return;
1685 };
1686
1687 let positions = &ecs.read_component::<comp::Pos>();
1688 let Some(pos) = positions.get(client.entity()) else {
1689 return;
1690 };
1691
1692 let weather = ecs.read_resource::<WeatherGrid>();
1693 const LINE_WIDTH: f32 = 0.05;
1696 {
1698 let Some(fluid) = phys.in_fluid else {
1699 return;
1700 };
1701 let shape = DebugShape::Line([pos.0, pos.0 + fluid.flow_vel().0 / 2.], LINE_WIDTH);
1702 let id = self.debug.add_shape(shape);
1703 lines.fluid_vel = Some(id);
1704 self.debug
1705 .set_context(id, [0.0; 4], [0.18, 0.72, 0.87, 0.8], [0.0, 0.0, 0.0, 1.0]);
1706 }
1707 {
1709 let Some(chunk) = client.current_chunk() else {
1710 return;
1711 };
1712 let shape = DebugShape::Line(
1713 [
1714 pos.0,
1715 pos.0
1716 + chunk
1717 .meta()
1718 .approx_chunk_terrain_normal()
1719 .unwrap_or(Vec3::unit_z())
1720 * 2.5,
1721 ],
1722 LINE_WIDTH,
1723 );
1724 let id = self.debug.add_shape(shape);
1725 lines.chunk_normal = Some(id);
1726 self.debug
1727 .set_context(id, [0.0; 4], [0.22, 0.63, 0.1, 0.8], [0.0, 0.0, 0.0, 1.0]);
1728 }
1729 {
1731 let wind = weather.get_interpolated(pos.0.xy()).wind_vel();
1732 let shape = DebugShape::Line([pos.0, pos.0 + wind * 5.0], LINE_WIDTH);
1733 let id = self.debug.add_shape(shape);
1734 lines.wind = Some(id);
1735 self.debug
1736 .set_context(id, [0.0; 4], [0.76, 0.76, 0.76, 0.8], [0.0, 0.0, 0.0, 1.0]);
1737 }
1738 {
1740 let shape = DebugShape::Line([pos.0, pos.0 + vel.0 / 2.0], LINE_WIDTH);
1741 let id = self.debug.add_shape(shape);
1742 lines.vel = Some(id);
1743 self.debug
1744 .set_context(id, [0.0; 4], [0.98, 0.76, 0.01, 0.8], [0.0, 0.0, 0.0, 1.0]);
1745 }
1746 }
1747 }
1748}