1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! Built-in material data: Pyrope, Almandine, Spessartine garnets.
use super::{CrystalSystem, GemMaterial, OpticalCharacter};
use crate::optics::{
absorption::{AbsorptionBand, AbsorptionTensor},
dispersion::DispersionModel,
};
use glam::Vec3;
/// Spessartine's absorption (Mn2+ lines and bands plus a UV charge-transfer edge), fitted to a
/// measured spectrum; see the comment at the entry below. A separate function so the entry stays
/// within clippy's function-length limit.
fn spessartine_absorption() -> AbsorptionTensor {
AbsorptionTensor::isotropic(vec![
// UV / O->Mn charge-transfer edge (stand-in, centre on the bound)
AbsorptionBand::energy(320.0, 7_023.0, 27.4),
// Mn2+ 6A1 -> 4E,4A1 (4G) group
AbsorptionBand::energy(411.0, 556.0, 14.2),
AbsorptionBand::energy(421.0, 419.0, 6.62),
AbsorptionBand::energy(429.0, 659.0, 8.24),
// Mn2+ 4T2 (G)
AbsorptionBand::energy(462.0, 1_551.0, 2.23),
AbsorptionBand::energy(485.0, 811.0, 1.59),
// Mn2+ 4T1 (G) + Fe2+ 505/520
AbsorptionBand::energy(523.0, 1_971.0, 1.57),
// weak 550-620 nm tail (sets T(600) = 0.86; without it L*80 C*78)
AbsorptionBand::energy(590.0, 3_000.0, 0.132),
])
}
impl GemMaterial {
/// Three of the five garnet-group species (the other two, Grossular and
/// Andradite, are [`Self::built_in_materials_garnets_grossular_and_andradite`] --
/// split purely to keep each part under clippy's function-length lint). All five
/// are cubic (isotropic, `birefringence_delta: 0.0`).
///
/// No primary Sellmeier/Cauchy fit is available for any of the five
/// (garnet-group gemstones are not present in the refractiveindex.info database);
/// every dispersion fit below is a 2-parameter Cauchy solved from `n_d` and its
/// "dispersion" figure treated as the standard gemological Fraunhofer B-G
/// interval, converted to F-C via the same 0.579 ratio as every other gemological
/// entry in this file -- see
/// [`Self::built_in_materials_aquamarine_through_citrine`]'s doc comment for the
/// full convention note. LOWER CONFIDENCE tier, same as Zircon/Topaz/
/// Tourmaline/Tanzanite/Emerald above; flagged for human cross-check.
pub(super) fn built_in_materials_garnets_pyrope_through_spessartine() -> Vec<Self> {
vec![
// Pyrope (Mg3Al2(SiO4)3): n_d=1.714, B-G 0.022 -> Delta n(F-C) = 0.01274,
// A=1.714-0.006668/0.5893^2=1.694804, B=0.006668. Abbe V_d ~ 56.05.
Self {
name: "Pyrope Garnet".to_string(),
crystal_system: CrystalSystem::Cubic,
optical_character: OpticalCharacter::Isotropic,
dispersion: DispersionModel::Cauchy {
a: 1.694_804,
b: 0.006_668,
c: 0.0,
},
birefringence_delta: 0.0,
// Chromophore: the classic Cr3+ (505nm)/Fe2+ (570nm) pair
// responsible for pyrope's deep red color. Widths (35nm/45nm) and
// peaks (1.2/1.4) TUNED for a clear deep red; band CENTRES cited.
absorption: AbsorptionTensor::isotropic(vec![
AbsorptionBand::new(505.0, 35.0, 1.2),
AbsorptionBand::new(570.0, 45.0, 1.4),
]),
c_axis: Vec3::Y,
biaxial_delta_beta_alpha: None,
scattering_sigma_s: 0.0,
scattering_g: 0.0,
edge_rounding_radius: 0.0,
absorption_path_scale: 1.0,
absorption_unit: super::AbsorptionUnit::ModelUnit,
zoning: None,
uniaxial_extraordinary_dispersion: None,
},
// Almandine (Fe3Al2(SiO4)3): n_d=1.790, B-G 0.024 -> Delta n(F-C) =
// 0.01390, A=1.790-0.007274/0.5893^2=1.769057, B=0.007274. Abbe V_d~56.86.
Self {
name: "Almandine Garnet".to_string(),
crystal_system: CrystalSystem::Cubic,
optical_character: OpticalCharacter::Isotropic,
dispersion: DispersionModel::Cauchy {
a: 1.769_057,
b: 0.007_274,
c: 0.0,
},
birefringence_delta: 0.0,
// Chromophore: the diagnostic Fe2+ 505/520/573nm triplet (almandine's
// characteristic three-line absorption pattern, the standard
// gemological identification feature for this species). Widths
// (20nm each) and peaks (1.3/1.5/1.3) TUNED for a deep red-brown/
// violet-red; band CENTRES cited.
absorption: AbsorptionTensor::isotropic(vec![
AbsorptionBand::new(505.0, 20.0, 1.3),
AbsorptionBand::new(520.0, 20.0, 1.5),
AbsorptionBand::new(573.0, 20.0, 1.3),
]),
c_axis: Vec3::Y,
biaxial_delta_beta_alpha: None,
scattering_sigma_s: 0.0,
scattering_g: 0.0,
edge_rounding_radius: 0.0,
absorption_path_scale: 1.0,
absorption_unit: super::AbsorptionUnit::ModelUnit,
zoning: None,
uniaxial_extraordinary_dispersion: None,
},
// Spessartine (Mn3Al2(SiO4)3): n_d=1.800, B-G 0.027 -> Delta n(F-C) =
// 0.01563, A=1.800-0.008182/0.5893^2=1.776445, B=0.008182. Abbe V_d~51.19.
Self {
name: "Spessartine Garnet".to_string(),
crystal_system: CrystalSystem::Cubic,
optical_character: OpticalCharacter::Isotropic,
dispersion: DispersionModel::Cauchy {
a: 1.776_445,
b: 0.008_182,
c: 0.0,
},
birefringence_delta: 0.0,
// Chromophore: Mn2+ spin-forbidden lines at 411/421/429 nm (the
// near-UV cut-off), the 462 and 485 nm Mn2+ bands and the weak 523 nm
// Mn2+/Fe2+ band, plus a UV charge-transfer edge (stand-in band at
// 320 nm). FITTED 2026-10-19 by a bounded
// least-squares energy-Gaussian fit (log residual, 380-780 nm) to the
// Caltech Mineral Spectroscopy Server spectrum of spessartine GRR 43
// (Rutherford Mine, Amelia, Virginia; 2.165 mm; baseline 0.030
// removed),
// `data/reference_spectra/caltech_spessartine_grr43_2p165mm.csv`,
// scaled to the 8.8 mm face-up path of a 7 mm stone at the sample's
// own (near end-member, Fe-poor "Mandarin") composition. Swatch
// (7 mm, D65): L*77 C*72 h=66; p = 1.5: h 63, p = 2: h 61. The
// Fe-richer GRR 72 from the same mine gives L*51 h 62.
absorption: spessartine_absorption(),
c_axis: Vec3::Y,
biaxial_delta_beta_alpha: None,
scattering_sigma_s: 0.0,
scattering_g: 0.0,
edge_rounding_radius: 0.0,
absorption_path_scale: 1.0,
absorption_unit: super::AbsorptionUnit::ModelUnit,
zoning: None,
uniaxial_extraordinary_dispersion: None,
},
]
}
/// The color-change garnet (Tanzanian pyrope-spessartine), split out of
/// [`Self::built_in_materials_garnets_pyrope_through_spessartine`] purely to keep each
/// part under clippy's function-length lint. Same 2-parameter Cauchy convention as the
/// three species above.
pub(super) fn built_in_materials_garnet_color_change() -> Vec<Self> {
vec![
// Color-Change Garnet: pyrope-spessartine solid solution (Mg,Mn)3Al2(SiO4)3 with
// traces of Cr3+ and/or V3+, Tanzania (Umba valley, Tanga; also Lindi/Tunduru).
// Cubic, isotropic. SG ~3.85 (comment only, not stored on the material).
//
// n_d=1.760 (typical range 1.755-1.765), roughly the midpoint of pyrope 1.714 and
// spessartine 1.800 (cross-check: `chromophore::catalogue::garnet_optics(
// &[("spessartine", 0.5)])` fills the remainder with pyrope and returns the
// linear mix 0.5*1.714+0.5*1.800 = 1.757, SG 3.88; the stored 1.760 is the
// typical gemmological value for the Tanzanian material and stays).
// Dispersion B-G 0.025 (between pyrope 0.022 and spessartine 0.027) -> Delta n(F-C)
// = 0.025*0.579 = 0.014475; B = 0.014475/(1/0.4861^2-1/0.6563^2) =
// 0.014475/1.91053 = 0.007576; A = 1.760-0.007576/0.5893^2 = 1.760-0.021816 =
// 1.738184. Abbe V_d ~ 52.5.
Self {
name: "Color-Change Garnet (Pyrope-Spessartine)".to_string(),
crystal_system: CrystalSystem::Cubic,
optical_character: OpticalCharacter::Isotropic,
dispersion: DispersionModel::Cauchy {
a: 1.738_184,
b: 0.007_576,
c: 0.0,
},
birefringence_delta: 0.0,
// Chromophores: Mn2+ 410/421/430nm (narrow spin-forbidden group, sigma
// 7.5-8nm = ~18-19nm FWHM, strong) blocks violet; Fe2+ 505nm weak (sigma
// 10.5nm = ~25nm FWHM); and the color-change band, ONE BROAD Cr3+/V3+ band
// centred ~573nm (sigma 40nm = ~94nm FWHM, strong). It leaves two
// transmission windows -- blue-green ~450-500nm and red >620nm -- and that
// balance is what swings the hue between daylight (D65: brownish/greyish
// mauve-green, the blue window dominates) and incandescent (3200K/A: the
// lamp is weak in the blue, so the red window wins: pink-red to
// orange-red). Band CENTRES cited; widths and peaks (1.5/1.2/1.5, 0.25,
// 1.3) TUNED, on the same band-weight scale as the garnets above.
absorption: AbsorptionTensor::isotropic(vec![
AbsorptionBand::new(410.0, 8.0, 1.5),
AbsorptionBand::new(421.0, 7.5, 1.2),
AbsorptionBand::new(430.0, 8.0, 1.5),
AbsorptionBand::new(505.0, 10.5, 0.25),
AbsorptionBand::new(573.0, 40.0, 1.3),
]),
c_axis: Vec3::Y,
biaxial_delta_beta_alpha: None,
scattering_sigma_s: 0.0,
scattering_g: 0.0,
edge_rounding_radius: 0.0,
absorption_path_scale: 1.0,
absorption_unit: super::AbsorptionUnit::ModelUnit,
zoning: None,
uniaxial_extraordinary_dispersion: None,
},
]
}
}