1use core::fmt;
4
5use euclid::default::Vector3D;
6use euclid::vec3;
7
8#[cfg(not(any(feature = "std", test)))]
10#[allow(
11 unused_imports,
12 reason = "unclear why this warns even though it is needed"
13)]
14use num_traits::float::Float as _;
15
16use crate::math::{PositiveSign, Rgb, ps32};
17
18#[cfg(doc)]
19use crate::space::{self, Space};
20
21pub(crate) type PackedLightScalar = u8;
23
24#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30#[repr(u8)]
31pub(crate) enum LightStatus {
32 Uninitialized = 0,
40 NoRays = 1,
42 Opaque = 128,
44 Visible = 255,
46}
47
48#[derive(Clone, Copy, PartialEq, Eq)]
51pub struct PackedLight {
52 value: Vector3D<PackedLightScalar>,
57 status: LightStatus,
58}
59impl PackedLight {
65 const LOG_SCALE: f32 = 10.0;
69 const LOG_OFFSET: f32 = 144.0;
70
71 pub(crate) const OPAQUE: Self = Self::none(LightStatus::Opaque);
73 pub(crate) const NO_RAYS: Self = Self::none(LightStatus::NoRays);
74 pub(crate) const UNINITIALIZED_AND_BLACK: Self = Self::none(LightStatus::Uninitialized);
75 pub(crate) const ONE: PackedLight = {
76 let one_scalar = Self::LOG_OFFSET as PackedLightScalar;
77 PackedLight {
78 status: LightStatus::Visible,
79 value: Vector3D::new(one_scalar, one_scalar, one_scalar),
80 }
81 };
82
83 pub(crate) fn some(value: Rgb) -> Self {
84 PackedLight {
85 value: Vector3D::new(
86 Self::scalar_in(value.red()),
87 Self::scalar_in(value.green()),
88 Self::scalar_in(value.blue()),
89 ),
90 status: LightStatus::Visible,
91 }
92 }
93
94 pub(crate) const fn none(status: LightStatus) -> Self {
95 PackedLight {
96 value: Vector3D::new(0, 0, 0),
97 status,
98 }
99 }
100
101 pub(crate) fn guess(value: Rgb) -> Self {
102 PackedLight {
103 value: Vector3D::new(
104 Self::scalar_in(value.red()),
105 Self::scalar_in(value.green()),
106 Self::scalar_in(value.blue()),
107 ),
108 status: LightStatus::Uninitialized,
109 }
110 }
111
112 #[inline]
114 pub fn value(self) -> Rgb {
115 Rgb::new_ps(
116 Self::scalar_out_ps(self.value.x),
117 Self::scalar_out_ps(self.value.y),
118 Self::scalar_out_ps(self.value.z),
119 )
120 }
121
122 pub(crate) fn status(self) -> LightStatus {
124 self.status
125 }
126
127 pub(crate) fn valid(self) -> bool {
131 self.status == LightStatus::Visible
132 }
133
134 pub(crate) fn value_with_ambient_occlusion(self) -> [f32; 4] {
139 [
140 Self::scalar_out(self.value.x),
141 Self::scalar_out(self.value.y),
142 Self::scalar_out(self.value.z),
143 match self.status {
144 LightStatus::Uninitialized => 0.0,
145 LightStatus::NoRays => 0.0,
146 LightStatus::Opaque => 0.25,
148 LightStatus::Visible => 1.0,
149 },
150 ]
151 }
152
153 #[inline]
154 #[doc(hidden)] pub fn as_texel(self) -> [u8; 4] {
156 let Self {
157 value: Vector3D {
158 x: r, y: g, z: b, ..
159 },
160 status,
161 } = self;
162 [r, g, b, status as u8]
163 }
164
165 #[doc(hidden)]
168 #[track_caller]
169 pub fn from_texel([r, g, b, s]: [u8; 4]) -> Self {
170 Self {
171 value: vec3(r, g, b),
172 status: match s {
173 0 => LightStatus::Uninitialized,
174 1 => LightStatus::NoRays,
175 128 => LightStatus::Opaque,
176 255 => LightStatus::Visible,
177 _ => panic!("invalid status value {s}"),
178 },
179 }
180 }
181
182 #[inline]
186 pub(crate) fn difference_priority(self, other: PackedLight) -> PackedLightScalar {
187 fn abs_diff(a: PackedLightScalar, b: PackedLightScalar) -> PackedLightScalar {
188 a.max(b) - a.min(b)
189 }
190 let mut difference = abs_diff(self.value.x, other.value.x)
191 .max(abs_diff(self.value.y, other.value.y))
192 .max(abs_diff(self.value.z, other.value.z));
193
194 if other.status != self.status {
195 difference = difference.saturating_add(PackedLightScalar::MAX / 4);
200 }
201
202 difference
203 }
204
205 #[inline(always)]
206 fn scalar_in(value: PositiveSign<f32>) -> PackedLightScalar {
207 (value.into_inner().log2() * Self::LOG_SCALE + Self::LOG_OFFSET).round()
209 as PackedLightScalar
210 }
211
212 #[inline(always)]
215 fn scalar_out(value: PackedLightScalar) -> f32 {
216 PACKED_LIGHT_SCALAR_LOOKUP_TABLE[usize::from(value)].into_inner()
217 }
218
219 #[inline(always)]
222 fn scalar_out_ps(value: PackedLightScalar) -> PositiveSign<f32> {
223 PACKED_LIGHT_SCALAR_LOOKUP_TABLE[usize::from(value)]
224 }
225
226 #[cfg(test)]
232 fn scalar_out_arithmetic(value: PackedLightScalar) -> f32 {
233 if value == 0 {
235 0.0
236 } else {
237 libm::exp2f((f32::from(value) - Self::LOG_OFFSET) / Self::LOG_SCALE)
242 }
243 }
244}
245
246impl fmt::Debug for PackedLight {
247 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
248 write!(
249 f,
250 "PackedLight({}, {}, {}, {:?})",
251 self.value.x, self.value.y, self.value.z, self.status
252 )
253 }
254}
255
256impl From<Rgb> for PackedLight {
257 #[inline]
258 fn from(value: Rgb) -> Self {
259 PackedLight::some(value)
260 }
261}
262
263#[cfg(feature = "save")]
264impl From<PackedLight> for crate::save::schema::LightSerV1 {
265 fn from(p: PackedLight) -> Self {
266 use crate::save::schema::LightStatusSerV1 as S;
267 crate::save::schema::LightSerV1 {
268 value: p.value.into(),
269 status: match p.status {
270 LightStatus::Uninitialized => S::Uninitialized,
271 LightStatus::NoRays => S::NoRays,
272 LightStatus::Opaque => S::Opaque,
273 LightStatus::Visible => S::Visible,
274 },
275 }
276 }
277}
278
279#[cfg(feature = "save")]
280impl From<crate::save::schema::LightSerV1> for PackedLight {
281 fn from(ls: crate::save::schema::LightSerV1) -> Self {
282 use crate::save::schema::LightStatusSerV1 as S;
283 PackedLight {
284 value: Vector3D::from(ls.value),
285 status: match ls.status {
286 S::Uninitialized => LightStatus::Uninitialized,
287 S::NoRays => LightStatus::NoRays,
288 S::Opaque => LightStatus::Opaque,
289 S::Visible => LightStatus::Visible,
290 },
291 }
292 }
293}
294
295#[rustfmt::skip]
300#[allow(clippy::approx_constant)]
301static PACKED_LIGHT_SCALAR_LOOKUP_TABLE: [PositiveSign<f32>; 256] = [
302 ps32(0.0), ps32(4.9575945e-5), ps32(5.3134198e-5), ps32(5.69478e-5), ps32(6.1035156e-5),
303 ps32(6.541588e-5), ps32(7.0110975e-5), ps32(7.51431e-5), ps32(8.053635e-5), ps32(8.6316744e-5),
304 ps32(9.251202e-5), ps32(9.915189e-5), ps32(0.000106268395), ps32(0.0001138956), ps32(0.00012207031),
305 ps32(0.00013083176), ps32(0.00014022195), ps32(0.0001502862), ps32(0.0001610727), ps32(0.00017263349),
306 ps32(0.00018502404), ps32(0.00019830378), ps32(0.00021253679), ps32(0.0002277912), ps32(0.00024414063),
307 ps32(0.00026166352), ps32(0.0002804439), ps32(0.0003005724), ps32(0.0003221454), ps32(0.00034526698),
308 ps32(0.00037004807), ps32(0.00039660756), ps32(0.00042507358), ps32(0.0004555824), ps32(0.00048828125),
309 ps32(0.00052332703), ps32(0.0005608878), ps32(0.0006011448), ps32(0.0006442908), ps32(0.00069053395),
310 ps32(0.00074009615), ps32(0.0007932151), ps32(0.00085014716), ps32(0.0009111648), ps32(0.0009765625),
311 ps32(0.0010466541), ps32(0.0011217756), ps32(0.0012022896), ps32(0.0012885816), ps32(0.0013810679),
312 ps32(0.0014801923), ps32(0.0015864302), ps32(0.0017002943), ps32(0.0018223296), ps32(0.001953125),
313 ps32(0.0020933081), ps32(0.0022435512), ps32(0.0024045792), ps32(0.0025771633), ps32(0.0027621358),
314 ps32(0.0029603846), ps32(0.0031728605), ps32(0.0034005886), ps32(0.0036446592), ps32(0.00390625),
315 ps32(0.004186615), ps32(0.0044871024), ps32(0.0048091584), ps32(0.005154328), ps32(0.0055242716),
316 ps32(0.0059207673), ps32(0.006345721), ps32(0.0068011773), ps32(0.0072893207), ps32(0.0078125),
317 ps32(0.00837323), ps32(0.008974205), ps32(0.009618317), ps32(0.010308656), ps32(0.011048543),
318 ps32(0.011841535), ps32(0.012691442), ps32(0.013602355), ps32(0.014578641), ps32(0.015625),
319 ps32(0.01674646), ps32(0.01794841), ps32(0.019236634), ps32(0.020617312), ps32(0.022097087),
320 ps32(0.02368307), ps32(0.025382884), ps32(0.02720471), ps32(0.029157283), ps32(0.03125),
321 ps32(0.03349292), ps32(0.03589682), ps32(0.038473267), ps32(0.041234624), ps32(0.044194173),
322 ps32(0.04736614), ps32(0.050765768), ps32(0.05440942), ps32(0.058314566), ps32(0.0625),
323 ps32(0.06698584), ps32(0.07179365), ps32(0.07694653), ps32(0.08246925), ps32(0.088388346),
324 ps32(0.09473228), ps32(0.10153155), ps32(0.108818814), ps32(0.11662913), ps32(0.125),
325 ps32(0.13397168), ps32(0.1435873), ps32(0.15389305), ps32(0.1649385), ps32(0.17677669),
326 ps32(0.18946455), ps32(0.2030631), ps32(0.21763763), ps32(0.23325826), ps32(0.25),
327 ps32(0.26794338), ps32(0.2871746), ps32(0.3077861), ps32(0.32987696), ps32(0.35355338),
328 ps32(0.37892914), ps32(0.4061262), ps32(0.43527526), ps32(0.4665165), ps32(0.5),
329 ps32(0.53588676), ps32(0.57434916), ps32(0.6155722), ps32(0.6597539), ps32(0.70710677),
330 ps32(0.7578583), ps32(0.8122524), ps32(0.8705506), ps32(0.933033), ps32(1.0),
331 ps32(1.0717734), ps32(1.1486983), ps32(1.2311444), ps32(1.319508), ps32(1.4142135),
332 ps32(1.5157166), ps32(1.6245048), ps32(1.7411011), ps32(1.866066), ps32(2.0),
333 ps32(2.143547), ps32(2.297397), ps32(2.4622889), ps32(2.6390157), ps32(2.828427),
334 ps32(3.031433), ps32(3.2490096), ps32(3.482202), ps32(3.732132), ps32(4.0),
335 ps32(4.2870936), ps32(4.594794), ps32(4.9245777), ps32(5.278032), ps32(5.656854),
336 ps32(6.0628657), ps32(6.498019), ps32(6.964404), ps32(7.4642644), ps32(8.0),
337 ps32(8.574187), ps32(9.189588), ps32(9.849155), ps32(10.556064), ps32(11.313708),
338 ps32(12.125731), ps32(12.996038), ps32(13.928808), ps32(14.928529), ps32(16.0),
339 ps32(17.148375), ps32(18.379171), ps32(19.698313), ps32(21.112127), ps32(22.627417),
340 ps32(24.251463), ps32(25.992073), ps32(27.857622), ps32(29.857058), ps32(32.0),
341 ps32(34.29675), ps32(36.758343), ps32(39.396626), ps32(42.224255), ps32(45.254833),
342 ps32(48.502926), ps32(51.984146), ps32(55.715244), ps32(59.714115), ps32(64.0),
343 ps32(68.5935), ps32(73.516685), ps32(78.79325), ps32(84.44851), ps32(90.50967),
344 ps32(97.00585), ps32(103.96829), ps32(111.43049), ps32(119.42823), ps32(128.0),
345 ps32(137.187), ps32(147.03337), ps32(157.5865), ps32(168.89702), ps32(181.01933),
346 ps32(194.0117), ps32(207.93658), ps32(222.86098), ps32(238.85646), ps32(256.0),
347 ps32(274.37408), ps32(294.06674), ps32(315.173), ps32(337.79395), ps32(362.03867),
348 ps32(388.02353), ps32(415.87317), ps32(445.72195), ps32(477.71277), ps32(512.0),
349 ps32(548.74817), ps32(588.1335), ps32(630.346), ps32(675.5879), ps32(724.07733),
350 ps32(776.04706), ps32(831.74634), ps32(891.4439), ps32(955.42554), ps32(1024.0),
351 ps32(1097.4963), ps32(1176.267), ps32(1260.692), ps32(1351.1758), ps32(1448.1547),
352 ps32(1552.0941), ps32(1663.4927), ps32(1782.8878), ps32(1910.8511), ps32(2048.0),
353 ps32(2194.9927),
354];
355
356#[cfg(test)]
357mod tests {
358 use super::*;
359 use crate::math::ps32;
360 use alloc::vec::Vec;
361 use core::iter::once;
362
363 fn packed_light_test_values() -> impl Iterator<Item = PackedLight> {
364 (PackedLightScalar::MIN..PackedLightScalar::MAX)
365 .flat_map(|s| {
366 vec![
367 PackedLight {
368 value: Vector3D::new(s, 0, 0),
369 status: LightStatus::Visible,
370 },
371 PackedLight {
372 value: Vector3D::new(0, s, 0),
373 status: LightStatus::Visible,
374 },
375 PackedLight {
376 value: Vector3D::new(0, 0, s),
377 status: LightStatus::Visible,
378 },
379 PackedLight {
380 value: Vector3D::new(s, 127, 255),
381 status: LightStatus::Visible,
382 },
383 ]
384 .into_iter()
385 })
386 .chain(once(PackedLight::OPAQUE))
387 .chain(once(PackedLight::NO_RAYS))
388 }
389
390 #[test]
393 fn packed_light_roundtrip() {
394 for i in PackedLightScalar::MIN..PackedLightScalar::MAX {
395 assert_eq!(i, PackedLight::scalar_in(PackedLight::scalar_out_ps(i)));
396 }
397 }
398
399 #[test]
402 fn packed_light_always_positive() {
403 for i in PackedLightScalar::MIN..PackedLightScalar::MAX {
404 let value = PackedLight::scalar_out(i);
405 assert!(value.is_finite() && value.is_sign_positive(), "{}", i);
406 }
407 }
408
409 #[test]
410 fn check_packed_light_table() {
411 let generated_table: Vec<f32> =
412 (0..=u8::MAX).map(PackedLight::scalar_out_arithmetic).collect();
413 print!("static PACKED_LIGHT_SCALAR_LOOKUP_TABLE: [PositiveSign<f32>; 256] = [");
414 for i in 0..=u8::MAX {
415 if i.is_multiple_of(5) {
416 print!("\n ");
417 }
418 print!(" ps32({:?}),", generated_table[i as usize]);
419 }
420 println!("\n];");
421
422 pretty_assertions::assert_eq!(
423 PACKED_LIGHT_SCALAR_LOOKUP_TABLE
424 .iter()
425 .copied()
426 .map(PositiveSign::<f32>::into_inner)
427 .collect::<Vec<_>>(),
428 generated_table
429 );
430 }
431
432 #[test]
434 fn packed_light_clipping_in() {
435 assert_eq!(
436 [
437 PackedLight::scalar_in(ps32(-0.0)),
438 PackedLight::scalar_in(ps32(0.0)),
439 PackedLight::scalar_in(ps32(1e-30)),
440 PackedLight::scalar_in(ps32(1e+30)),
441 PackedLight::scalar_in(PositiveSign::<f32>::INFINITY),
442 ],
443 [0, 0, 0, 255, 255],
444 );
445 }
446
447 #[test]
448 fn packed_light_rounds_to_nearest() {
449 for i in PackedLightScalar::MIN..PackedLightScalar::MAX {
450 let value = PackedLight::scalar_out_ps(i);
451 assert_eq!(
452 (
453 PackedLight::scalar_in(value * ps32(0.9999)),
454 i,
455 PackedLight::scalar_in(value * ps32(1.0001)),
456 ),
457 (i, i, i)
458 );
459 }
460 }
461
462 #[test]
463 fn packed_light_is_packed() {
464 assert_eq!(size_of::<PackedLight>(), 4);
466 }
467
468 #[test]
470 fn packed_light_extreme_values_out() {
471 assert_eq!(
472 [
473 PackedLight::scalar_out(0),
474 PackedLight::scalar_out(1),
475 PackedLight::scalar_out(2),
476 PackedLight::scalar_out(254),
477 PackedLight::scalar_out(255),
478 ],
479 [0.0, 4.9575945e-5, 5.3134198e-5, 2048.0, 2194.9927],
480 );
481 }
482
483 #[test]
484 fn packed_light_difference_vs_eq() {
485 for v1 in packed_light_test_values() {
486 for v2 in packed_light_test_values() {
487 assert_eq!(
488 v1 == v2,
489 v1.difference_priority(v2) == 0,
490 "v1={v1:?} v2={v2:?}"
491 );
492 }
493 }
494 }
495}