1use molgfx_math::{Rgba8, Vec3};
8use serde::{Deserialize, Serialize};
9
10#[derive(Clone, Copy, PartialEq, Debug, Serialize, Deserialize)]
12pub struct LightingEnvironment {
13 pub zenith: Rgba8,
15 pub horizon: Rgba8,
17 pub ground: Rgba8,
19 pub rim_color: Rgba8,
21 pub key_color: Rgba8,
23 pub fill_color: Rgba8,
25 pub key_direction: Vec3,
27 pub fill_direction: Vec3,
29 pub diffuse_strength: f32,
31 pub specular_strength: f32,
33 pub rim_strength: f32,
35 pub key_strength: f32,
37 pub fill_strength: f32,
39 pub key_angular_radius: f32,
41 pub shadow_strength: f32,
43}
44
45impl LightingEnvironment {
46 #[must_use]
48 pub const fn neutral() -> Self {
49 Self {
50 zenith: Rgba8::opaque(170, 184, 204),
51 horizon: Rgba8::opaque(118, 126, 138),
52 ground: Rgba8::opaque(72, 66, 60),
53 rim_color: Rgba8::opaque(132, 156, 202),
54 key_color: Rgba8::opaque(255, 244, 230),
55 fill_color: Rgba8::opaque(151, 181, 224),
56 key_direction: Vec3::new(-0.42, 0.58, 0.70),
57 fill_direction: Vec3::new(0.78, -0.12, 0.50),
58 diffuse_strength: 0.72,
59 specular_strength: 0.58,
60 rim_strength: 0.22,
61 key_strength: 1.55,
62 fill_strength: 0.28,
63 key_angular_radius: 0.16,
64 shadow_strength: 0.48,
65 }
66 }
67
68 #[must_use]
70 pub const fn documentary() -> Self {
71 Self {
72 zenith: Rgba8::opaque(226, 232, 240),
80 horizon: Rgba8::opaque(188, 194, 202),
81 ground: Rgba8::opaque(150, 146, 141),
82 fill_color: Rgba8::opaque(206, 214, 226),
83 rim_color: Rgba8::opaque(120, 132, 152),
84 diffuse_strength: 0.62,
85 specular_strength: 0.72,
86 rim_strength: 0.16,
87 key_strength: 1.35,
88 fill_strength: 0.30,
89 key_angular_radius: 0.20,
90 shadow_strength: 0.66,
91 ..Self::neutral()
92 }
93 }
94
95 pub(crate) fn sanitize(self) -> Self {
96 let neutral = Self::neutral();
97 Self {
98 key_direction: direction(self.key_direction, neutral.key_direction),
99 fill_direction: direction(self.fill_direction, neutral.fill_direction),
100 diffuse_strength: bounded(self.diffuse_strength, 4.0, neutral.diffuse_strength),
101 specular_strength: bounded(self.specular_strength, 4.0, neutral.specular_strength),
102 rim_strength: bounded(self.rim_strength, 2.0, neutral.rim_strength),
103 key_strength: bounded(self.key_strength, 8.0, neutral.key_strength),
104 fill_strength: bounded(self.fill_strength, 8.0, neutral.fill_strength),
105 key_angular_radius: bounded(self.key_angular_radius, 0.5, neutral.key_angular_radius),
106 shadow_strength: bounded(self.shadow_strength, 1.0, neutral.shadow_strength),
107 ..self
108 }
109 }
110
111 pub(crate) fn blend(self, other: Self, weight: f32) -> Self {
112 let weight = unit(weight);
113 let choose = |current, next| if weight >= 0.5 { next } else { current };
114 Self {
115 zenith: choose(self.zenith, other.zenith),
116 horizon: choose(self.horizon, other.horizon),
117 ground: choose(self.ground, other.ground),
118 rim_color: choose(self.rim_color, other.rim_color),
119 key_color: choose(self.key_color, other.key_color),
120 fill_color: choose(self.fill_color, other.fill_color),
121 key_direction: self.key_direction.lerp(other.key_direction, weight),
122 fill_direction: self.fill_direction.lerp(other.fill_direction, weight),
123 diffuse_strength: lerp(self.diffuse_strength, other.diffuse_strength, weight),
124 specular_strength: lerp(self.specular_strength, other.specular_strength, weight),
125 rim_strength: lerp(self.rim_strength, other.rim_strength, weight),
126 key_strength: lerp(self.key_strength, other.key_strength, weight),
127 fill_strength: lerp(self.fill_strength, other.fill_strength, weight),
128 key_angular_radius: lerp(self.key_angular_radius, other.key_angular_radius, weight),
129 shadow_strength: lerp(self.shadow_strength, other.shadow_strength, weight),
130 }
131 .sanitize()
132 }
133
134 pub(crate) fn packed(self) -> [[f32; 4]; 8] {
135 let lighting = self.sanitize();
136 let lane = |color: Rgba8, strength| {
137 let rgb = super::backdrop::linear_rgb(color);
138 [rgb[0], rgb[1], rgb[2], strength]
139 };
140 [
141 lane(lighting.zenith, lighting.diffuse_strength),
142 lane(lighting.horizon, lighting.specular_strength),
143 lane(lighting.ground, lighting.rim_strength),
144 lane(lighting.rim_color, lighting.shadow_strength),
145 lighting
146 .key_direction
147 .extend(lighting.key_strength)
148 .to_array(),
149 lane(lighting.key_color, lighting.key_angular_radius),
150 lighting
151 .fill_direction
152 .extend(lighting.fill_strength)
153 .to_array(),
154 lane(lighting.fill_color, 0.0),
155 ]
156 }
157}
158
159impl Default for LightingEnvironment {
160 fn default() -> Self {
161 Self::documentary()
162 }
163}
164
165fn direction(value: Vec3, fallback: Vec3) -> Vec3 {
166 if value.is_finite() && value.length_squared() > 1.0e-8 {
167 value.normalize()
168 } else {
169 fallback.normalize()
170 }
171}
172
173fn bounded(value: f32, maximum: f32, fallback: f32) -> f32 {
174 if value.is_finite() {
175 value.clamp(0.0, maximum)
176 } else {
177 fallback
178 }
179}
180
181fn unit(value: f32) -> f32 {
182 bounded(value, 1.0, 0.0)
183}
184
185fn lerp(from: f32, to: f32, weight: f32) -> f32 {
186 from + (to - from) * weight
187}