#[derive(Debug, Clone, Copy, PartialEq)]
pub struct BodyColorPreset {
pub key: &'static str,
pub label: &'static str,
pub absorption_rgb: [f32; 3],
}
pub const BODY_COLOR_PRESETS: [BodyColorPreset; 9] = [
BodyColorPreset {
key: "clear",
label: "Clear",
absorption_rgb: [0.0, 0.0, 0.0],
},
BodyColorPreset {
key: "blue",
label: "Blue",
absorption_rgb: [2.8, 1.2, 0.1],
},
BodyColorPreset {
key: "red",
label: "Red",
absorption_rgb: [0.1, 2.5, 2.2],
},
BodyColorPreset {
key: "green",
label: "Green",
absorption_rgb: [2.2, 0.2, 2.0],
},
BodyColorPreset {
key: "violet",
label: "Violet",
absorption_rgb: [1.8, 1.6, 0.2],
},
BodyColorPreset {
key: "yellow",
label: "Yellow",
absorption_rgb: [0.2, 0.4, 2.8],
},
BodyColorPreset {
key: "pink",
label: "Pink",
absorption_rgb: [0.4, 2.2, 1.6],
},
BodyColorPreset {
key: "teal",
label: "Teal",
absorption_rgb: [1.2, 0.4, 0.1],
},
BodyColorPreset {
key: "amber",
label: "Amber",
absorption_rgb: [0.2, 0.6, 1.8],
},
];
#[must_use]
#[expect(
clippy::float_cmp,
reason = "a preset is recognised only by its exact stored triple, as the doc says"
)]
pub fn preset_index_for_rgb(rgb: [f32; 3]) -> Option<usize> {
BODY_COLOR_PRESETS
.iter()
.position(|preset| preset.absorption_rgb == rgb)
}
#[must_use]
pub fn preset_label_for_rgb(rgb: [f32; 3]) -> Option<&'static str> {
preset_index_for_rgb(rgb).map(|index| BODY_COLOR_PRESETS[index].label)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::optics::absorption::legacy_rgb_bands;
#[test]
fn every_preset_finds_its_own_index() {
for (index, preset) in BODY_COLOR_PRESETS.iter().enumerate() {
assert_eq!(preset_index_for_rgb(preset.absorption_rgb), Some(index));
assert_eq!(
preset_label_for_rgb(preset.absorption_rgb),
Some(preset.label)
);
}
}
#[test]
fn an_unlisted_triple_matches_no_preset() {
assert_eq!(preset_index_for_rgb([0.5, 0.5, 0.5]), None);
assert_eq!(preset_label_for_rgb([0.5, 0.5, 0.5]), None);
}
fn absorption_at(rgb: [f32; 3], lambda_nm: f32) -> f32 {
legacy_rgb_bands(rgb)
.iter()
.map(|band| band.evaluate(lambda_nm))
.sum()
}
#[test]
fn teal_absorbs_red_and_amber_absorbs_blue() {
let teal = BODY_COLOR_PRESETS[7].absorption_rgb;
let amber = BODY_COLOR_PRESETS[8].absorption_rgb;
assert_eq!(BODY_COLOR_PRESETS[7].key, "teal");
assert_eq!(BODY_COLOR_PRESETS[8].key, "amber");
let (teal_red, teal_green, teal_blue) = (
absorption_at(teal, 620.0),
absorption_at(teal, 540.0),
absorption_at(teal, 450.0),
);
assert!(
teal_red > teal_green && teal_red > teal_blue,
"Teal must absorb most at 620 nm (red {teal_red}, green {teal_green}, blue {teal_blue})"
);
let (amber_red, amber_green, amber_blue) = (
absorption_at(amber, 620.0),
absorption_at(amber, 540.0),
absorption_at(amber, 450.0),
);
assert!(
amber_blue > amber_red && amber_blue > amber_green,
"Amber must absorb most at 450 nm (red {amber_red}, green {amber_green}, blue {amber_blue})"
);
}
#[test]
fn keys_and_labels_are_unique() {
for (i, a) in BODY_COLOR_PRESETS.iter().enumerate() {
for b in &BODY_COLOR_PRESETS[i + 1..] {
assert_ne!(a.key, b.key);
assert_ne!(a.label, b.label);
}
}
}
}