indicatrix 0.8.1

A physically-based spectral gemstone renderer that turns GemCAD-style cutting instructions into rendered output.
Documentation
// ---------------------------------------------------------------------------------
// P6 exit-event spectral splitting: the three pure (no-binding)
// per-channel helpers optics::raytracer::refraction's own top-of-file "Exit-event
// spectral splitting" doc comment names -- `compute_channel_transmission`,
// `compute_uniaxial_exit_transmission`, and `narrow_compat`. Every operation, and its
// order, is a direct transcription of the CPU function it mirrors (`f32::mul_add` ->
// `fma`, same clamp bounds, same intermediate names where WGSL's lack of tuple returns
// allows it) -- see each function's own comment for the exact CPU counterpart.
//
// `compute_channel_transmission`/`compute_uniaxial_exit_transmission` are private
// (module-private, not even `pub(super)`) inside `refraction.rs`, and `narrow_compat`
// is `pub(super)` (visible only within `optics::raytracer`, not from this crate's
// `renderer` tree) -- `refraction.rs` is on the protected/coordinator-owned
// list, so its visibility cannot be widened to import these directly the way
// `p2_uniaxial_fresnel.rs` imports the (already `pub(crate)`) `entry_solve_pair`/
// `internal_solve`. `renderer::gpu::transport_check::p6_exit_splitting`'s own CPU
// reference functions are therefore verbatim transcriptions of the real CPU source
// (cross-referenced by file/line in that module's doc comment) rather than a call
// through the module boundary -- the one deliberate exception to this file tree's
// usual "always call the real CPU function" rule, forced by that protection boundary,
// not a design choice.
//
// Constants duplicated locally (`8u` in place of `NUM_CHANNELS`, an inlined `1.0 -
// 1e-6` in place of `DIRECTION_MATCH_COS_TOL`) rather than referencing
// `spectral_transport.wgsl`'s own copies: `build.rs` concatenates THIS file ahead of
// both `spectral_transport.wgsl` and `transport_functions.wgsl` (see this file's own
// header comment), so a name defined only in one of those two files is not yet in
// scope here -- exactly the same reason `R_UNPOL_MIN`/`R_UNPOL_MAX` above are this
// file's own copies rather than a reference to `spectral_transport.wgsl`'s
// `R_UNPOL_SELECT_MIN`/`MAX`.
// ---------------------------------------------------------------------------------

// optics::raytracer::refraction::compute_channel_transmission. WGSL has no `Option`, so
// `azimuth_valid == false` stands in for the Rust `None` case (same convention as
// `entry_eigenmode_selection`'s own `valid` field above) -- callers pass
// `false`/`0.0`/`0.0` for `azimuth_valid`/`cos_2psi_x`/`sin_2psi_x` whenever
// `entering_anisotropic` is false or the entry mode-selection draw was invalid.
struct ChannelTransmissionW {
    transmitted: vec4<f32>,
    r_unpol_k: f32,
}

fn compute_channel_transmission(
    n1k: f32,
    n2k: f32,
    cos_i: f32,
    cos_t_k: f32,
    r_unpol: f32,
    entering_anisotropic: bool,
    azimuth_valid: bool,
    cos_2psi_x: f32,
    sin_2psi_x: f32,
    incident_stokes_k: vec4<f32>,
) -> ChannelTransmissionW {
    let t_s_k = (2.0 * n1k * cos_i) / fma(n2k, cos_t_k, n1k * cos_i);
    let t_p_k = (2.0 * n1k * cos_i) / fma(n1k, cos_t_k, n2k * cos_i);
    let trans_matrix_k = mueller_fresnel_transmission(n1k, n2k, cos_i, cos_t_k, t_s_k, t_p_k);
    var incident_k = incident_stokes_k;
    if (entering_anisotropic && azimuth_valid) {
        let i_k = incident_stokes_k.x;
        incident_k = vec4<f32>(i_k, i_k * cos_2psi_x, i_k * sin_2psi_x, 0.0);
    }
    let transmitted = (trans_matrix_k * incident_k) * (1.0 / (1.0 - r_unpol));
    let r_s_k = fma(n2k, -cos_t_k, n1k * cos_i) / fma(n2k, cos_t_k, n1k * cos_i);
    let r_p_k = fma(n1k, -cos_t_k, n2k * cos_i) / fma(n1k, cos_t_k, n2k * cos_i);
    let r_unpol_k = clamp(0.5 * fma(r_p_k, r_p_k, r_s_k * r_s_k), 1e-4, 1.0 - 1e-4);
    var result: ChannelTransmissionW;
    result.transmitted = transmitted;
    result.r_unpol_k = r_unpol_k;
    return result;
}

// optics::raytracer::refraction::compute_uniaxial_exit_transmission.
struct UniaxialExitTransmissionW {
    transmitted: vec4<f32>,
    i_unit: f32,
}

fn compute_uniaxial_exit_transmission(
    sol: InternalSolW,
    r_branch: f32,
    incident_i: f32,
) -> UniaxialExitTransmissionW {
    let flux_inc = max(sol.flux_inc, 1e-12);
    let ts_n = cplx_scale(sol.t_s, sqrt(sol.flux_ts / flux_inc));
    let tp_n = cplx_scale(sol.t_p, sqrt(sol.flux_tp / flux_inc));
    let i_unit = cplx_norm_sqr(ts_n) + cplx_norm_sqr(tp_n);
    let q_unit = cplx_norm_sqr(ts_n) - cplx_norm_sqr(tp_n);
    let cross_st = cplx_mul(ts_n, cplx_conj(tp_n));
    let u_unit = 2.0 * cross_st.re;
    let v_unit = -2.0 * cross_st.im;
    let transmitted = vec4<f32>(
        incident_i * i_unit, incident_i * q_unit, incident_i * u_unit, incident_i * v_unit,
    ) * (1.0 / (1.0 - r_branch));
    var result: UniaxialExitTransmissionW;
    result.transmitted = transmitted;
    result.i_unit = i_unit;
    return result;
}

// optics::raytracer::refraction::narrow_compat. `compat[c]` bit `j` set means channels
// `c`/`j` have refracted within the direction-match tolerance of each other at every
// interior dispersive event so far -- see `ExitSplitCtx::compat`'s own CPU-side doc
// comment. Hero is always channel 0 in this kernel's convention (`path_pdf[0]`
// throughout `spectral_transport.wgsl`), so this is specialized to that fixed hero
// index rather than taking one as a parameter, unlike the CPU function's `hero: usize`.
// `dirs_valid[k] == false` stands in for the CPU `dirs[k]: Option<Vec3> == None` case.
fn narrow_compat(
    compat: ptr<function, array<u32, 8>>,
    dirs: array<vec3<f32>, 8>,
    dirs_valid: array<bool, 8>,
    hero_match: array<bool, 8>,
) {
    let direction_match_cos_tol = 1.0 - 1e-6;
    for (var a: u32 = 0u; a < 8u; a = a + 1u) {
        for (var b: u32 = a + 1u; b < 8u; b = b + 1u) {
            var matches_ab: bool;
            if (a == 0u) {
                matches_ab = hero_match[b];
            } else if (b == 0u) {
                matches_ab = hero_match[a];
            } else if (dirs_valid[a] && dirs_valid[b]) {
                matches_ab = dot(dirs[a], dirs[b]) >= direction_match_cos_tol;
            } else {
                matches_ab = true;
            }
            if (!matches_ab) {
                (*compat)[a] = (*compat)[a] & ~(1u << b);
                (*compat)[b] = (*compat)[b] & ~(1u << a);
            }
        }
    }
}