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