use bevy_asset::Handle;
use bevy_camera::{
primitives::{CubeMapFace, CubemapFrusta, CubemapLayout, Frustum, Sphere, CUBE_MAP_FACES},
visibility::{self, CubemapVisibleEntities, ViewVisibility, Visibility, VisibilityClass},
};
use bevy_color::Color;
use bevy_ecs::prelude::*;
use bevy_image::Image;
use bevy_math::{primitives::ViewFrustum, Mat4};
use bevy_reflect::prelude::*;
use bevy_transform::components::{GlobalTransform, Transform};
use crate::{cluster::ClusterVisibilityClass, 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 shadow_maps_enabled: bool,
pub contact_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,
shadow_maps_enabled: false,
contact_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, FromTemplate)]
#[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_bounding_spheres(
mut commands: Commands,
point_lights_query: Query<
(Entity, &PointLight, &GlobalTransform),
Or<(Changed<PointLight>, Changed<GlobalTransform>)>,
>,
) {
for (point_light_entity, point_light, global_transform) in &point_lights_query {
commands.entity(point_light_entity).insert(Sphere {
center: global_transform.translation_vec3a(),
radius: point_light.range,
});
}
}
pub fn update_point_light_frusta(
mut views: Query<
(
&GlobalTransform,
&PointLight,
&mut CubemapFrusta,
&ViewVisibility,
),
Or<(
Changed<GlobalTransform>,
Changed<PointLight>,
Changed<ViewVisibility>,
)>,
>,
) {
let view_rotations = CUBE_MAP_FACES
.iter()
.map(|CubeMapFace { target, up }| Transform::IDENTITY.looking_at(*target, *up))
.collect::<Vec<_>>();
for (transform, point_light, mut cubemap_frusta, view_visibility) in &mut views {
if !point_light.shadow_maps_enabled || !view_visibility.get() {
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(ViewFrustum::from_clip_from_world_custom_far(
&clip_from_world,
&transform.translation(),
&view_backward,
point_light.range,
));
}
}
}