primase_core/light_unit.rs
1//! The unit the world's lights were lowered from (`core.light_unit`, a world resource).
2
3use serde::Serialize;
4use serde_json::Value;
5
6use crate::{JsonError, Resource, World};
7
8/// What one unit of the source's own value of a point or spot light is in candela: the calibration the lowering that
9/// made the world's point and spot `core.light`s chose (a directional light's intensity, in lux, is not in it). Such a
10/// light's intensity is its source value times `candela_per_unit`, so `colour * intensity / candela_per_unit` is the
11/// value the source stored, the one a program of the source reads its lights in ([`crate::shader::LightValue::Colour`]). The lowering that makes the lights writes it; a world whose lights no
12/// lowering made (a scene read in candela) has none, and a light table's colours have no value there.
13#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
14pub struct LightUnit {
15 /// Candela per unit of the source's light value; finite and above 0.
16 pub candela_per_unit: f32,
17}
18
19impl Resource for LightUnit {
20 const NAME: &'static str = "core.light_unit";
21 fn to_json(&self) -> Result<Option<Value>, JsonError> {
22 crate::json::to_value(self).map(Some)
23 }
24 /// A finite factor above 0: a light of the source's value 1 has some intensity.
25 fn verify(&self, _w: &World) -> Result<(), String> {
26 if !(self.candela_per_unit.is_finite() && self.candela_per_unit > 0.0) {
27 return Err(format!(
28 "candela per unit {} is not finite and above 0",
29 self.candela_per_unit
30 ));
31 }
32 Ok(())
33 }
34}
35
36/// What one unit of the source's own value of a directional light (its sun) is in lux: the calibration the lowering that
37/// made the scene's sun (`core.environment`'s) chose. The sun's intensity is its source value times `lux_per_unit`, so
38/// `color * intensity / lux_per_unit` is the value the source stored, the one a program of the source reads its sun in
39/// ([`crate::shader::FrameParameter::LightColour`]). The lowering that makes the sun writes it; a world whose sun no
40/// lowering made has none.
41#[derive(Clone, Copy, Debug, PartialEq, Serialize)]
42pub struct DirectionalLightUnit {
43 /// Lux per unit of the source's directional light value; finite and above 0.
44 pub lux_per_unit: f32,
45}
46
47impl Resource for DirectionalLightUnit {
48 const NAME: &'static str = "core.directional_light_unit";
49 fn to_json(&self) -> Result<Option<Value>, JsonError> {
50 crate::json::to_value(self).map(Some)
51 }
52 /// A finite factor above 0: a sun of the source's value 1 has some intensity.
53 fn verify(&self, _w: &World) -> Result<(), String> {
54 if !(self.lux_per_unit.is_finite() && self.lux_per_unit > 0.0) {
55 return Err(format!(
56 "lux per unit {} is not finite and above 0",
57 self.lux_per_unit
58 ));
59 }
60 Ok(())
61 }
62}
63
64#[cfg(test)]
65mod tests {
66 use super::*;
67
68 /// The directional unit is held to the same: a finite factor above 0.
69 #[test]
70 fn the_directional_unit_is_a_finite_factor_above_zero() {
71 let w = World::new();
72 for bad in [0.0, -1.0, f32::NAN, f32::INFINITY] {
73 assert!(
74 DirectionalLightUnit { lux_per_unit: bad }.verify(&w).is_err(),
75 "{bad}"
76 );
77 }
78 assert_eq!(DirectionalLightUnit { lux_per_unit: 2.0 }.verify(&w), Ok(()));
79 }
80
81 /// A factor of 0, a negative one or a non-finite one is refused; any finite one above 0 is kept.
82 #[test]
83 fn the_unit_is_a_finite_factor_above_zero() {
84 let w = World::new();
85 for bad in [0.0, -1.0, f32::NAN, f32::INFINITY] {
86 assert!(
87 LightUnit {
88 candela_per_unit: bad
89 }
90 .verify(&w)
91 .is_err(),
92 "{bad}"
93 );
94 }
95 for good in [1.0, 0.001, 25.0] {
96 assert_eq!(
97 LightUnit {
98 candela_per_unit: good
99 }
100 .verify(&w),
101 Ok(())
102 );
103 }
104 }
105}