use crate::color::Color;
use glamx::Vec3;
pub const MAX_LIGHTS: usize = 8;
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum LightType {
Point {
attenuation_radius: f32,
},
Directional(Vec3),
Spot {
inner_cone_angle: f32,
outer_cone_angle: f32,
attenuation_radius: f32,
},
}
impl Default for LightType {
fn default() -> Self {
LightType::Point {
attenuation_radius: 100.0,
}
}
}
#[derive(Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Light {
pub light_type: LightType,
pub color: Color,
pub intensity: f32,
pub radius: f32,
pub enabled: bool,
pub casts_shadows: bool,
#[cfg_attr(feature = "serde", serde(default = "all_layers"))]
pub layers: u32,
}
#[cfg(feature = "serde")]
fn all_layers() -> u32 {
u32::MAX
}
impl Default for Light {
fn default() -> Self {
Self {
light_type: LightType::default(),
color: crate::color::WHITE,
intensity: 3.0,
radius: 0.0,
enabled: true,
casts_shadows: true,
layers: u32::MAX,
}
}
}
impl Light {
pub fn point(attenuation_radius: f32) -> Self {
Self {
light_type: LightType::Point { attenuation_radius },
..Default::default()
}
}
pub fn directional(dir: Vec3) -> Self {
Self {
light_type: LightType::Directional(dir),
..Default::default()
}
}
pub fn spot(inner_cone_angle: f32, outer_cone_angle: f32, attenuation_radius: f32) -> Self {
Self {
light_type: LightType::Spot {
inner_cone_angle,
outer_cone_angle,
attenuation_radius,
},
..Default::default()
}
}
pub fn with_color(mut self, color: Color) -> Self {
self.color = color;
self
}
pub fn with_intensity(mut self, intensity: f32) -> Self {
self.intensity = intensity;
self
}
pub fn with_enabled(mut self, enabled: bool) -> Self {
self.enabled = enabled;
self
}
pub fn with_radius(mut self, radius: f32) -> Self {
self.radius = radius.max(0.0);
self
}
pub fn with_casts_shadows(mut self, casts_shadows: bool) -> Self {
self.casts_shadows = casts_shadows;
self
}
pub fn with_layers(mut self, layers: u32) -> Self {
self.layers = layers;
self
}
}
#[derive(Clone, Debug)]
pub struct CollectedLight {
pub light_type: LightType,
pub color: Vec3,
pub intensity: f32,
pub world_position: Vec3,
pub world_direction: Vec3,
pub radius: f32,
pub casts_shadows: bool,
pub layers: u32,
}
#[derive(Copy, Clone, Debug, PartialEq, Default)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub enum FogMode {
#[default]
Off,
Linear { start: f32, end: f32 },
Exponential { density: f32 },
ExponentialSquared { density: f32 },
}
#[derive(Copy, Clone, Debug, PartialEq)]
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
pub struct Fog {
pub color: Color,
pub mode: FogMode,
pub height_falloff: f32,
}
impl Default for Fog {
fn default() -> Self {
Self {
color: crate::color::Color::new(0.6, 0.7, 0.8, 1.0),
mode: FogMode::Off,
height_falloff: 0.0,
}
}
}
impl Fog {
pub fn linear(color: Color, start: f32, end: f32) -> Self {
Self {
color,
mode: FogMode::Linear { start, end },
height_falloff: 0.0,
}
}
pub fn exponential(color: Color, density: f32) -> Self {
Self {
color,
mode: FogMode::Exponential { density },
height_falloff: 0.0,
}
}
pub fn exponential_squared(color: Color, density: f32) -> Self {
Self {
color,
mode: FogMode::ExponentialSquared { density },
height_falloff: 0.0,
}
}
pub fn with_height_falloff(mut self, height_falloff: f32) -> Self {
self.height_falloff = height_falloff.max(0.0);
self
}
pub(crate) fn params(&self) -> [f32; 4] {
match self.mode {
FogMode::Off => [0.0, 0.0, 0.0, 0.0],
FogMode::Linear { start, end } => [1.0, start, end, self.height_falloff],
FogMode::Exponential { density } => [2.0, density, 0.0, self.height_falloff],
FogMode::ExponentialSquared { density } => [3.0, density, 0.0, self.height_falloff],
}
}
}
#[derive(Clone, Debug)]
pub struct LightCollection {
pub lights: Vec<CollectedLight>,
pub ambient: f32,
pub ambient_color: Color,
pub fog: Fog,
}
impl Default for LightCollection {
fn default() -> Self {
Self::new()
}
}
impl LightCollection {
pub fn new() -> Self {
Self {
lights: Vec::with_capacity(MAX_LIGHTS),
ambient: 0.2,
ambient_color: crate::color::WHITE,
fog: Fog::default(),
}
}
pub fn with_ambient(ambient: f32) -> Self {
Self {
lights: Vec::with_capacity(MAX_LIGHTS),
ambient,
ambient_color: crate::color::WHITE,
fog: Fog::default(),
}
}
pub fn add(&mut self, light: CollectedLight) -> bool {
self.lights.push(light);
true
}
pub fn is_full(&self) -> bool {
self.lights.len() >= MAX_LIGHTS
}
pub fn split_primary_clustered(&self) -> (Vec<usize>, Vec<usize>) {
let n = self.lights.len();
if n <= MAX_LIGHTS {
return ((0..n).collect(), Vec::new());
}
fn tier(l: &CollectedLight) -> u8 {
if l.casts_shadows {
0
} else if matches!(l.light_type, LightType::Directional(_)) {
1
} else {
2
}
}
let mut order: Vec<usize> = (0..n).collect();
order.sort_by(|&a, &b| {
let (la, lb) = (&self.lights[a], &self.lights[b]);
tier(la).cmp(&tier(lb)).then_with(|| {
lb.intensity
.partial_cmp(&la.intensity)
.unwrap_or(std::cmp::Ordering::Equal)
})
});
let clustered = order.split_off(MAX_LIGHTS);
(order, clustered)
}
pub fn len(&self) -> usize {
self.lights.len()
}
pub fn is_empty(&self) -> bool {
self.lights.is_empty()
}
pub fn clear(&mut self) {
self.lights.clear();
}
}