use crate::optics::{
dispersion::DispersionModel,
materials::{CrystalSystem, GemMaterial, OpticalCharacter},
};
#[test]
fn the_absorption_path_scale_builder_ignores_degenerate_values() {
let base = GemMaterial::sapphire().with_absorption_path_scale(1.5);
assert_eq!(base.absorption_path_scale, 1.5);
for bad in [0.0, -2.0, f32::NAN, f32::INFINITY, f32::NEG_INFINITY] {
let kept = base.clone().with_absorption_path_scale(bad);
assert_eq!(kept.absorption_path_scale, 1.5, "{bad} must be ignored");
}
let fresh = GemMaterial::sapphire().with_absorption_path_scale(f32::NAN);
assert_eq!(fresh.absorption_path_scale, 1.0);
assert_eq!(
base.with_absorption_path_scale(2.25).absorption_path_scale,
2.25
);
}
#[test]
fn every_builtin_has_an_explicit_scattering_arm() {
for material in GemMaterial::all_materials() {
assert!(
GemMaterial::recommended_scattering_arm(&material.name).is_some(),
"{} has no explicit recommended_scattering arm -- it silently falls \
through to the generic default",
material.name
);
}
}
#[test]
fn new_custom_dispersion_delta_measures_exactly_at_f_and_c() {
for dispersion_delta in [0.005f32, 0.010, 0.02564, 0.05] {
let material =
GemMaterial::new_custom("F-C probe", 1.6, dispersion_delta, 0.0, [0.0, 0.0, 0.0]);
let n_f = material.dispersion.evaluate(486.1);
let n_c = material.dispersion.evaluate(656.3);
let measured = n_f - n_c;
assert!(
(measured - dispersion_delta).abs() < 1e-4,
"requested F-C delta {dispersion_delta} but measured {measured} \
(n_F={n_f}, n_C={n_c})"
);
}
}
#[test]
fn new_custom_preserves_mean_ri_at_sodium_d_line_regardless_of_dispersion_delta() {
for dispersion_delta in [0.0f32, 0.01, 0.03] {
let material =
GemMaterial::new_custom("D-line probe", 1.72, dispersion_delta, 0.0, [0.0, 0.0, 0.0]);
let n_d = material.dispersion.evaluate(589.3);
assert!(
(n_d - 1.72).abs() < 1e-4,
"dispersion_delta={dispersion_delta}: n_d should stay 1.72, got {n_d}"
);
}
}
#[test]
fn new_custom_with_dispersion_keeps_the_model_and_its_n_d() {
let model = GemMaterial::by_name("Glass (N-BK7)")
.expect("N-BK7 is a built-in")
.dispersion;
assert!(
matches!(model, DispersionModel::Sellmeier3 { .. }),
"test premise: N-BK7 is a three-term Sellmeier"
);
let material = GemMaterial::new_custom_with_dispersion("BK7 copy", model, 0.0, [0.0; 3]);
assert_eq!(material.dispersion, model);
assert_eq!(
material.dispersion.evaluate(589.3).to_bits(),
model.n_d().to_bits()
);
assert!((model.n_d() - 1.5168).abs() < 0.002, "{}", model.n_d());
let mut expected = GemMaterial::new_custom("BK7 copy", model.n_d(), 0.0, 0.0, [0.0; 3]);
expected.dispersion = model;
assert_eq!(material, expected);
}
#[test]
fn new_custom_with_dispersion_derives_the_rest_like_new_custom() {
let model = DispersionModel::Cauchy {
a: 1.7,
b: 0.006,
c: 0.0,
};
let uniaxial = GemMaterial::new_custom_with_dispersion("Probe", model, -0.008, [0.2, 0.4, 2.8]);
assert_eq!(uniaxial.crystal_system, CrystalSystem::Trigonal);
assert_eq!(
uniaxial.optical_character,
OpticalCharacter::UniaxialNegative
);
assert_eq!(
uniaxial.birefringence_delta.to_bits(),
(-0.008_f32).to_bits()
);
assert_ne!(
uniaxial.absorption,
GemMaterial::new_custom("Probe", 1.7, 0.0, 0.0, [0.0; 3]).absorption,
"the colour reaches the absorption"
);
let simple = GemMaterial::new_custom("Same", 1.62, 0.02, 0.0, [0.0; 3]);
let rebuilt = GemMaterial::new_custom_with_dispersion("Same", simple.dispersion, 0.0, [0.0; 3]);
assert_eq!(
rebuilt, simple,
"a Cauchy fit passed back in changes nothing"
);
}
#[test]
fn with_body_color_changes_only_the_absorption() {
let yellow = crate::optics::materials::body_color::BODY_COLOR_PRESETS[5];
assert_eq!(
yellow.key, "yellow",
"test premise: index 5 is the yellow preset"
);
let base = GemMaterial::sapphire();
let colored = base.clone().with_body_color(yellow.absorption_rgb);
assert_eq!(colored.name, base.name);
assert_eq!(colored.dispersion, base.dispersion);
assert_eq!(
colored.birefringence_delta.to_bits(),
base.birefringence_delta.to_bits()
);
assert_eq!(
colored.c_axis.to_array().map(f32::to_bits),
base.c_axis.to_array().map(f32::to_bits)
);
assert_ne!(
colored.absorption, base.absorption,
"the color variant must actually change the absorption"
);
assert_eq!(
colored.absorption,
crate::optics::absorption::AbsorptionTensor::isotropic(
crate::optics::absorption::legacy_rgb_bands(yellow.absorption_rgb)
)
);
let mut restored = colored;
restored.absorption = base.absorption.clone();
assert_eq!(restored, base);
}