use molgfx_math::{Rgba8, Vec3};
use serde::{Deserialize, Serialize};
#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
pub struct LightingEnvironment {
pub zenith: Rgba8,
pub horizon: Rgba8,
pub ground: Rgba8,
pub rim_color: Rgba8,
pub key_color: Rgba8,
pub fill_color: Rgba8,
pub key_direction: Vec3,
pub fill_direction: Vec3,
pub diffuse_strength: f32,
pub specular_strength: f32,
pub rim_strength: f32,
pub key_strength: f32,
pub fill_strength: f32,
pub key_angular_radius: f32,
pub shadow_strength: f32,
}
impl LightingEnvironment {
#[must_use]
pub const fn neutral() -> Self {
Self {
zenith: Rgba8::opaque(170, 184, 204),
horizon: Rgba8::opaque(118, 126, 138),
ground: Rgba8::opaque(72, 66, 60),
rim_color: Rgba8::opaque(132, 156, 202),
key_color: Rgba8::opaque(255, 244, 230),
fill_color: Rgba8::opaque(151, 181, 224),
key_direction: Vec3::new(-0.42, 0.58, 0.70),
fill_direction: Vec3::new(0.78, -0.12, 0.50),
diffuse_strength: 0.72,
specular_strength: 0.58,
rim_strength: 0.22,
key_strength: 1.55,
fill_strength: 0.28,
key_angular_radius: 0.16,
shadow_strength: 0.48,
}
}
#[must_use]
pub const fn documentary() -> Self {
Self {
zenith: Rgba8::opaque(226, 232, 240),
horizon: Rgba8::opaque(188, 194, 202),
ground: Rgba8::opaque(150, 146, 141),
fill_color: Rgba8::opaque(206, 214, 226),
rim_color: Rgba8::opaque(120, 132, 152),
diffuse_strength: 0.62,
specular_strength: 0.72,
rim_strength: 0.16,
key_strength: 1.35,
fill_strength: 0.30,
key_angular_radius: 0.20,
shadow_strength: 0.66,
..Self::neutral()
}
}
pub(crate) fn sanitize(self) -> Self {
let neutral = Self::neutral();
Self {
key_direction: direction(self.key_direction, neutral.key_direction),
fill_direction: direction(self.fill_direction, neutral.fill_direction),
diffuse_strength: bounded(self.diffuse_strength, 4.0, neutral.diffuse_strength),
specular_strength: bounded(self.specular_strength, 4.0, neutral.specular_strength),
rim_strength: bounded(self.rim_strength, 2.0, neutral.rim_strength),
key_strength: bounded(self.key_strength, 8.0, neutral.key_strength),
fill_strength: bounded(self.fill_strength, 8.0, neutral.fill_strength),
key_angular_radius: bounded(self.key_angular_radius, 0.5, neutral.key_angular_radius),
shadow_strength: bounded(self.shadow_strength, 1.0, neutral.shadow_strength),
..self
}
}
pub(crate) fn blend(self, other: Self, weight: f32) -> Self {
let weight = unit(weight);
let choose = |current, next| if weight >= 0.5 { next } else { current };
Self {
zenith: choose(self.zenith, other.zenith),
horizon: choose(self.horizon, other.horizon),
ground: choose(self.ground, other.ground),
rim_color: choose(self.rim_color, other.rim_color),
key_color: choose(self.key_color, other.key_color),
fill_color: choose(self.fill_color, other.fill_color),
key_direction: self.key_direction.lerp(other.key_direction, weight),
fill_direction: self.fill_direction.lerp(other.fill_direction, weight),
diffuse_strength: lerp(self.diffuse_strength, other.diffuse_strength, weight),
specular_strength: lerp(self.specular_strength, other.specular_strength, weight),
rim_strength: lerp(self.rim_strength, other.rim_strength, weight),
key_strength: lerp(self.key_strength, other.key_strength, weight),
fill_strength: lerp(self.fill_strength, other.fill_strength, weight),
key_angular_radius: lerp(self.key_angular_radius, other.key_angular_radius, weight),
shadow_strength: lerp(self.shadow_strength, other.shadow_strength, weight),
}
.sanitize()
}
pub(crate) fn packed(self) -> [[f32; 4]; 8] {
let lighting = self.sanitize();
let lane = |color: Rgba8, strength| {
let rgb = super::backdrop::linear_rgb(color);
[rgb[0], rgb[1], rgb[2], strength]
};
[
lane(lighting.zenith, lighting.diffuse_strength),
lane(lighting.horizon, lighting.specular_strength),
lane(lighting.ground, lighting.rim_strength),
lane(lighting.rim_color, lighting.shadow_strength),
lighting
.key_direction
.extend(lighting.key_strength)
.to_array(),
lane(lighting.key_color, lighting.key_angular_radius),
lighting
.fill_direction
.extend(lighting.fill_strength)
.to_array(),
lane(lighting.fill_color, 0.0),
]
}
}
impl Default for LightingEnvironment {
fn default() -> Self {
Self::documentary()
}
}
fn direction(value: Vec3, fallback: Vec3) -> Vec3 {
if value.is_finite() && value.length_squared() > 1.0e-8 {
value.normalize()
} else {
fallback.normalize()
}
}
fn bounded(value: f32, maximum: f32, fallback: f32) -> f32 {
if value.is_finite() {
value.clamp(0.0, maximum)
} else {
fallback
}
}
fn unit(value: f32) -> f32 {
bounded(value, 1.0, 0.0)
}
fn lerp(from: f32, to: f32, weight: f32) -> f32 {
from + (to - from) * weight
}