use core::mem::offset_of;
use crate::optics::{
absorption::{AbsorptionBand, BandShape},
dispersion::DispersionModel,
materials::{CrystalSystem, GemMaterial, OpticalCharacter},
};
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct DispersionParams {
pub model_type: u32,
_pad_after_model_type: [u32; 3],
pub param_a: [f32; 4],
pub param_b: [f32; 4],
pub param_c: [f32; 4],
pub c_axis_and_birefringence: [f32; 4],
pub is_anisotropic: u32,
pub biaxial_delta_beta_alpha: f32,
pub has_biaxial_delta: u32,
_pad_tail: f32,
}
const _: () = {
assert!(offset_of!(DispersionParams, model_type) == 0);
assert!(offset_of!(DispersionParams, param_a) == 16);
assert!(offset_of!(DispersionParams, param_b) == 32);
assert!(offset_of!(DispersionParams, param_c) == 48);
assert!(offset_of!(DispersionParams, c_axis_and_birefringence) == 64);
assert!(offset_of!(DispersionParams, is_anisotropic) == 80);
assert!(offset_of!(DispersionParams, biaxial_delta_beta_alpha) == 84);
assert!(offset_of!(DispersionParams, has_biaxial_delta) == 88);
assert!(offset_of!(DispersionParams, _pad_tail) == 92);
assert!(size_of::<DispersionParams>() == 96);
};
pub mod dispersion_model_type {
pub const SELLMEIER1: u32 = 0;
pub const SELLMEIER3: u32 = 1;
pub const CAUCHY: u32 = 2;
}
pub const MAX_ABSORPTION_BANDS: usize = 8;
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuAbsorptionBand {
pub center_nm: f32,
pub width_nm: f32,
pub peak: f32,
pub shape: u32,
}
const _: () = {
assert!(offset_of!(GpuAbsorptionBand, center_nm) == 0);
assert!(offset_of!(GpuAbsorptionBand, width_nm) == 4);
assert!(offset_of!(GpuAbsorptionBand, peak) == 8);
assert!(offset_of!(GpuAbsorptionBand, shape) == 12);
assert!(size_of::<GpuAbsorptionBand>() == 16);
};
pub mod band_shape {
pub const GAUSSIAN_WAVELENGTH: u32 = 0;
pub const GAUSSIAN_ENERGY: u32 = 1;
}
pub mod crystal_system {
pub const CUBIC: u32 = 0;
pub const TETRAGONAL: u32 = 1;
pub const HEXAGONAL: u32 = 2;
pub const TRIGONAL: u32 = 3;
pub const ORTHORHOMBIC: u32 = 4;
pub const MONOCLINIC: u32 = 5;
pub const TRICLINIC: u32 = 6;
}
pub mod optical_character {
pub const ISOTROPIC: u32 = 0;
pub const UNIAXIAL_POSITIVE: u32 = 1;
pub const UNIAXIAL_NEGATIVE: u32 = 2;
pub const BIAXIAL_POSITIVE: u32 = 3;
pub const BIAXIAL_NEGATIVE: u32 = 4;
}
#[repr(C)]
#[derive(Copy, Clone, Debug, bytemuck::Pod, bytemuck::Zeroable)]
pub struct GpuGemMaterial {
pub dispersion: DispersionParams,
pub crystal_system: u32,
pub optical_character: u32,
pub is_pleochroic: u32,
pub o_ray_band_count: u32,
pub e_ray_band_count: u32,
pub o_ray_bands: [GpuAbsorptionBand; MAX_ABSORPTION_BANDS],
pub e_ray_bands: [GpuAbsorptionBand; MAX_ABSORPTION_BANDS],
pub scattering_sigma_s: f32,
pub scattering_g: f32,
pub edge_rounding_radius: f32,
pub has_beta_ray: u32,
pub beta_ray_band_count: u32,
pub beta_ray_bands: [GpuAbsorptionBand; MAX_ABSORPTION_BANDS],
pub absorption_path_scale: f32,
pub has_extraordinary_dispersion: u32,
pub extraordinary_model_type: u32,
_pad_before_extraordinary_params: [u32; 3],
pub extraordinary_param_a: [f32; 4],
pub extraordinary_param_b: [f32; 4],
pub zones: super::zoning::GpuZoneTable,
}
const _: () = {
assert!(offset_of!(GpuGemMaterial, dispersion) == 0);
assert!(offset_of!(GpuGemMaterial, crystal_system) == 96);
assert!(offset_of!(GpuGemMaterial, optical_character) == 100);
assert!(offset_of!(GpuGemMaterial, is_pleochroic) == 104);
assert!(offset_of!(GpuGemMaterial, o_ray_band_count) == 108);
assert!(offset_of!(GpuGemMaterial, e_ray_band_count) == 112);
assert!(offset_of!(GpuGemMaterial, o_ray_bands) == 116);
assert!(offset_of!(GpuGemMaterial, e_ray_bands) == 244);
assert!(offset_of!(GpuGemMaterial, scattering_sigma_s) == 372);
assert!(offset_of!(GpuGemMaterial, scattering_g) == 376);
assert!(offset_of!(GpuGemMaterial, edge_rounding_radius) == 380);
assert!(offset_of!(GpuGemMaterial, has_beta_ray) == 384);
assert!(offset_of!(GpuGemMaterial, beta_ray_band_count) == 388);
assert!(offset_of!(GpuGemMaterial, beta_ray_bands) == 392);
assert!(offset_of!(GpuGemMaterial, absorption_path_scale) == 520);
assert!(offset_of!(GpuGemMaterial, has_extraordinary_dispersion) == 524);
assert!(offset_of!(GpuGemMaterial, extraordinary_model_type) == 528);
assert!(offset_of!(GpuGemMaterial, extraordinary_param_a) == 544);
assert!(offset_of!(GpuGemMaterial, extraordinary_param_b) == 560);
assert!(offset_of!(GpuGemMaterial, zones) == 576);
assert!(size_of::<GpuGemMaterial>() == 576 + 2128);
};
impl GpuGemMaterial {
#[must_use]
pub fn encode(material: &GemMaterial) -> Self {
let crystal_system_val = match material.crystal_system {
CrystalSystem::Cubic => crystal_system::CUBIC,
CrystalSystem::Tetragonal => crystal_system::TETRAGONAL,
CrystalSystem::Hexagonal => crystal_system::HEXAGONAL,
CrystalSystem::Trigonal => crystal_system::TRIGONAL,
CrystalSystem::Orthorhombic => crystal_system::ORTHORHOMBIC,
CrystalSystem::Monoclinic => crystal_system::MONOCLINIC,
CrystalSystem::Triclinic => crystal_system::TRICLINIC,
};
let optical_character_val = match material.optical_character {
OpticalCharacter::Isotropic => optical_character::ISOTROPIC,
OpticalCharacter::UniaxialPositive => optical_character::UNIAXIAL_POSITIVE,
OpticalCharacter::UniaxialNegative => optical_character::UNIAXIAL_NEGATIVE,
OpticalCharacter::BiaxialPositive => optical_character::BIAXIAL_POSITIVE,
OpticalCharacter::BiaxialNegative => optical_character::BIAXIAL_NEGATIVE,
};
let (absorption, zones) = super::zoning::material_parts(material);
let (o_ray_bands, o_ray_band_count) = encode_bands(&absorption.o_ray);
let (e_ray_bands, e_ray_band_count) = encode_bands(&absorption.e_ray);
let (beta_ray_bands, beta_ray_band_count) = absorption
.beta_ray
.as_deref()
.map_or_else(empty_bands, encode_bands);
let (
has_extraordinary_dispersion,
extraordinary_model_type,
extraordinary_param_a,
extraordinary_param_b,
) = material.uniaxial_extraordinary_dispersion.map_or(
(0u32, 0u32, [0.0f32; 4], [0.0f32; 4]),
|e_dispersion| {
let (model_type, param_a, param_b) = encode_dispersion_model(&e_dispersion);
(1u32, model_type, param_a, param_b)
},
);
Self {
dispersion: DispersionParams::encode(material),
crystal_system: crystal_system_val,
optical_character: optical_character_val,
is_pleochroic: u32::from(absorption.is_pleochroic),
o_ray_band_count,
e_ray_band_count,
o_ray_bands,
e_ray_bands,
scattering_sigma_s: material.scattering_sigma_s,
scattering_g: material.scattering_g,
edge_rounding_radius: material.edge_rounding_radius,
has_beta_ray: u32::from(absorption.beta_ray.is_some()),
beta_ray_band_count,
beta_ray_bands,
absorption_path_scale: material.absorption_path_scale,
has_extraordinary_dispersion,
extraordinary_model_type,
_pad_before_extraordinary_params: [0; 3],
extraordinary_param_a,
extraordinary_param_b,
zones,
}
}
}
#[must_use]
const fn encode_dispersion_model(model: &DispersionModel) -> (u32, [f32; 4], [f32; 4]) {
match *model {
DispersionModel::Sellmeier1 { b1, c1 } => (
dispersion_model_type::SELLMEIER1,
[b1, 0.0, 0.0, 0.0],
[c1, 0.0, 0.0, 0.0],
),
DispersionModel::Sellmeier3 { b, c } => (
dispersion_model_type::SELLMEIER3,
[b[0], b[1], b[2], 0.0],
[c[0], c[1], c[2], 0.0],
),
DispersionModel::Cauchy { a, b, c } => {
(dispersion_model_type::CAUCHY, [a, b, c, 0.0], [0.0; 4])
}
}
}
impl DispersionParams {
#[must_use]
fn encode(material: &GemMaterial) -> Self {
let (model_type, param_a, param_b) = encode_dispersion_model(&material.dispersion);
let c_axis = material.c_axis;
Self {
model_type,
_pad_after_model_type: [0; 3],
param_a,
param_b,
param_c: [0.0; 4],
c_axis_and_birefringence: [c_axis.x, c_axis.y, c_axis.z, material.birefringence_delta],
is_anisotropic: u32::from(
material.crystal_system != CrystalSystem::Cubic
&& material.birefringence_delta.abs() > 1e-4,
),
biaxial_delta_beta_alpha: material.biaxial_delta_beta_alpha.unwrap_or(0.0),
has_biaxial_delta: u32::from(material.biaxial_delta_beta_alpha.is_some()),
_pad_tail: 0.0,
}
}
}
pub(super) const fn empty_bands() -> ([GpuAbsorptionBand; MAX_ABSORPTION_BANDS], u32) {
(
[GpuAbsorptionBand {
center_nm: 0.0,
width_nm: 1.0,
peak: 0.0,
shape: band_shape::GAUSSIAN_WAVELENGTH,
}; MAX_ABSORPTION_BANDS],
0,
)
}
pub(super) fn encode_bands(
bands: &[AbsorptionBand],
) -> ([GpuAbsorptionBand; MAX_ABSORPTION_BANDS], u32) {
if bands.len() > MAX_ABSORPTION_BANDS {
tracing::warn!(
band_count = bands.len(),
max = MAX_ABSORPTION_BANDS,
"material has more absorption bands than the GPU encoding holds; truncating"
);
}
let bands = &bands[..bands.len().min(MAX_ABSORPTION_BANDS)];
let mut out = [GpuAbsorptionBand {
center_nm: 0.0,
width_nm: 1.0,
peak: 0.0,
shape: band_shape::GAUSSIAN_WAVELENGTH,
}; MAX_ABSORPTION_BANDS];
for (slot, band) in out.iter_mut().zip(bands.iter()) {
*slot = GpuAbsorptionBand {
center_nm: band.center_nm,
width_nm: band.width_nm,
peak: band.peak,
shape: match band.shape {
BandShape::GaussianWavelength => band_shape::GAUSSIAN_WAVELENGTH,
BandShape::GaussianEnergy => band_shape::GAUSSIAN_ENERGY,
},
};
}
(
out,
u32::try_from(bands.len()).expect("the band count is truncated to MAX_ABSORPTION_BANDS"),
)
}