use bevy_asset::Handle;
use bevy_camera::{
primitives::{CubeMapFace, CubemapFrusta, CubemapLayout, Frustum, CUBE_MAP_FACES},
visibility::{self, CubemapVisibleEntities, Visibility, VisibilityClass},
};
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::Mat4;
use bevy_reflect::prelude::*;
use bevy_transform::components::{GlobalTransform, Transform};
use crate::{
cluster::{ClusterVisibilityClass, GlobalVisibleClusterableObjects},
light_consts,
};
#[derive(Component, Debug, Clone, Copy, Reflect)]
#[reflect(Component, Default, Debug, Clone)]
#[require(
CubemapFrusta,
CubemapVisibleEntities,
Transform,
Visibility,
VisibilityClass
)]
#[component(on_add = visibility::add_visibility_class::<ClusterVisibilityClass>)]
pub struct PointLight {
pub color: Color,
pub intensity: f32,
pub range: f32,
pub radius: f32,
pub shadows_enabled: bool,
#[cfg(feature = "experimental_pbr_pcss")]
pub soft_shadows_enabled: bool,
pub affects_lightmapped_mesh_diffuse: bool,
pub shadow_depth_bias: f32,
pub shadow_normal_bias: f32,
pub shadow_map_near_z: f32,
}
impl Default for PointLight {
fn default() -> Self {
PointLight {
color: Color::WHITE,
intensity: light_consts::lumens::VERY_LARGE_CINEMA_LIGHT,
range: 20.0,
radius: 0.0,
shadows_enabled: false,
affects_lightmapped_mesh_diffuse: true,
shadow_depth_bias: Self::DEFAULT_SHADOW_DEPTH_BIAS,
shadow_normal_bias: Self::DEFAULT_SHADOW_NORMAL_BIAS,
shadow_map_near_z: Self::DEFAULT_SHADOW_MAP_NEAR_Z,
#[cfg(feature = "experimental_pbr_pcss")]
soft_shadows_enabled: false,
}
}
}
impl PointLight {
pub const DEFAULT_SHADOW_DEPTH_BIAS: f32 = 0.08;
pub const DEFAULT_SHADOW_NORMAL_BIAS: f32 = 0.6;
pub const DEFAULT_SHADOW_MAP_NEAR_Z: f32 = 0.1;
}
#[derive(Clone, Component, Debug, Reflect)]
#[reflect(Component, Debug)]
#[require(PointLight)]
pub struct PointLightTexture {
pub image: Handle<Image>,
pub cubemap_layout: CubemapLayout,
}
#[derive(Resource, Clone, Debug, Reflect)]
#[reflect(Resource, Debug, Default, Clone)]
pub struct PointLightShadowMap {
pub size: usize,
}
impl Default for PointLightShadowMap {
fn default() -> Self {
Self { size: 1024 }
}
}
pub fn update_point_light_frusta(
global_lights: Res<GlobalVisibleClusterableObjects>,
mut views: Query<(Entity, &GlobalTransform, &PointLight, &mut CubemapFrusta)>,
changed_lights: Query<
Entity,
(
With<PointLight>,
Or<(Changed<GlobalTransform>, Changed<PointLight>)>,
),
>,
) {
let view_rotations = CUBE_MAP_FACES
.iter()
.map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
.collect::<Vec<_>>();
for (entity, transform, point_light, mut cubemap_frusta) in &mut views {
if !global_lights.is_changed() && !changed_lights.contains(entity) {
continue;
}
if !point_light.shadows_enabled || !global_lights.entities.contains(&entity) {
continue;
}
let clip_from_view = Mat4::perspective_infinite_reverse_rh(
core::f32::consts::FRAC_PI_2,
1.0,
point_light.shadow_map_near_z,
);
let view_translation = Transform::from_translation(transform.translation());
let view_backward = transform.back();
for (view_rotation, frustum) in view_rotations.iter().zip(cubemap_frusta.iter_mut()) {
let world_from_view = view_translation * *view_rotation;
let clip_from_world = clip_from_view * world_from_view.compute_affine().inverse();
*frustum = Frustum::from_clip_from_world_custom_far(
&clip_from_world,
&transform.translation(),
&view_backward,
point_light.range,
);
}
}
}