use super::{
context::{
BounceContext, BounceRay, BounceState, ExitEvent, PathModeState, RayMaterialContext,
RngDraw, UniaxialBounceContext,
},
geometry::{BounceRefractionGeometry, poynting_dir_for_mode},
reflect_refract::{RefractSelection, apply_partial_reflect_bounce, apply_refract_bounce},
uniaxial_entry::{apply_uniaxial_entry_bounce, entry_eigenmode_selection},
uniaxial_internal::apply_uniaxial_internal_bounce,
};
use crate::optics::{
polarization::StokesVector,
raytracer::sampling::{BIREFRINGENT_SPLIT_STREAM, FRESNEL_BRANCH_STREAM, hash_u32},
};
use glam::Vec3;
type FresnelBounceResult = (Vec3, Vec3, bool, Option<bool>, Option<f32>);
fn try_dispatch_uniaxial_bounce(
bctx: &BounceContext<'_, '_>,
ray: BounceRay,
mode_state: PathModeState,
entering_anisotropic: bool,
rng: RngDraw,
state: &mut BounceState<'_>,
exit_event: &mut ExitEvent<'_, '_>,
) -> Option<FresnelBounceResult> {
let (ctx, geo) = (bctx.ctx, bctx.geo);
let BounceRay { k_hat, .. } = ray;
let PathModeState {
inside_gem,
is_extraordinary,
..
} = mode_state;
if entering_anisotropic && let Some(frame) = geo.uniaxial_frame {
let ubctx = UniaxialBounceContext {
ctx,
geo,
frame: &frame,
};
let (new_k, new_s, new_inside_gem, is_extraordinary_update) =
apply_uniaxial_entry_bounce(&ubctx, ray, rng, state, exit_event.exit);
return Some((new_k, new_s, new_inside_gem, is_extraordinary_update, None));
}
if inside_gem
&& let Some(frame) = geo.uniaxial_frame
&& k_hat.cross(ctx.c_axis).length_squared() > 1e-6
{
let ubctx = UniaxialBounceContext {
ctx,
geo,
frame: &frame,
};
return Some(apply_uniaxial_internal_bounce(
&ubctx,
is_extraordinary,
ray,
rng,
state,
exit_event,
));
}
None
}
struct EntryModeSelectionInputs<'m, 'b> {
ctx: &'b RayMaterialContext<'m>,
geo: &'b BounceRefractionGeometry,
entering_anisotropic: bool,
current_plane_normal: Vec3,
k_hat: Vec3,
hero_stokes: StokesVector,
is_extraordinary: bool,
rng_seed: u32,
bounce: u32,
}
fn resolve_entry_mode_selection(
inputs: &EntryModeSelectionInputs<'_, '_>,
) -> (bool, Option<(f32, f32)>) {
let &EntryModeSelectionInputs {
ctx,
geo,
entering_anisotropic,
current_plane_normal,
k_hat,
hero_stokes,
is_extraordinary,
rng_seed,
bounce,
} = inputs;
let entry_selection = if entering_anisotropic && !geo.is_biaxial {
entry_eigenmode_selection(ctx.c_axis, current_plane_normal, k_hat, hero_stokes)
} else {
None
};
let p_o = entry_selection.map_or(0.5, |(p, ..)| p);
let mode_split_rand = (hash_u32(rng_seed ^ hash_u32(bounce ^ BIREFRINGENT_SPLIT_STREAM))
as f32)
/ 4_294_967_295.0;
let use_extraordinary = if entering_anisotropic {
mode_split_rand < (1.0 - p_o)
} else {
is_extraordinary
};
let entry_mode_azimuth2 = entry_selection.map(|(_, cos_2psi_o, sin_2psi_o)| {
if use_extraordinary {
(-cos_2psi_o, -sin_2psi_o)
} else {
(cos_2psi_o, sin_2psi_o)
}
});
(use_extraordinary, entry_mode_azimuth2)
}
fn compute_entry_reflect_probability(
geo: &BounceRefractionGeometry,
entering_anisotropic: bool,
use_extraordinary: bool,
min: f32,
max: f32,
) -> f32 {
let ordinary_selected = entering_anisotropic && !geo.is_biaxial && !use_extraordinary;
let n2 = if ordinary_selected {
geo.n_o_hero
} else {
geo.n2
};
let sin2_t = if ordinary_selected {
let eta = geo.n1 / n2;
eta * eta * geo.cos_i.mul_add(-geo.cos_i, 1.0)
} else {
geo.sin2_t
};
let cos_t = (1.0 - sin2_t).max(0.0).sqrt();
let r_s =
f32::mul_add(n2, -cos_t, geo.n1 * geo.cos_i) / f32::mul_add(n2, cos_t, geo.n1 * geo.cos_i);
let r_p =
f32::mul_add(geo.n1, -cos_t, n2 * geo.cos_i) / f32::mul_add(geo.n1, cos_t, n2 * geo.cos_i);
let r_unpol_raw = 0.5 * r_p.mul_add(r_p, r_s * r_s);
r_unpol_raw.clamp(min, max)
}
pub(in crate::optics::raytracer) fn apply_partial_fresnel_bounce(
bctx: &BounceContext<'_, '_>,
ray: BounceRay,
mode_state: PathModeState,
rng: RngDraw,
state: &mut BounceState<'_>,
exit_event: &mut ExitEvent<'_, '_>,
) -> (Vec3, Vec3, bool, Option<bool>, Option<f32>) {
const R_UNPOL_SELECT_MIN: f32 = 0.02;
const R_UNPOL_SELECT_MAX: f32 = 0.98;
let (ctx, geo) = (bctx.ctx, bctx.geo);
let BounceRay { k_hat, normal } = ray;
let PathModeState {
current_plane_normal,
inside_gem,
is_extraordinary,
} = mode_state;
let RngDraw { rng_seed, bounce } = rng;
let entering_anisotropic = !inside_gem && ctx.is_anisotropic;
if let Some(result) = try_dispatch_uniaxial_bounce(
bctx,
ray,
mode_state,
entering_anisotropic,
rng,
state,
exit_event,
) {
return result;
}
let (use_extraordinary, entry_mode_azimuth2) =
resolve_entry_mode_selection(&EntryModeSelectionInputs {
ctx,
geo,
entering_anisotropic,
current_plane_normal,
k_hat,
hero_stokes: state.stokes[ctx.hero_idx],
is_extraordinary,
rng_seed,
bounce,
});
let r_unpol = compute_entry_reflect_probability(
geo,
entering_anisotropic,
use_extraordinary,
R_UNPOL_SELECT_MIN,
R_UNPOL_SELECT_MAX,
);
let rng_bounce =
(hash_u32(rng_seed ^ hash_u32(bounce ^ FRESNEL_BRANCH_STREAM)) as f32) / 4_294_967_295.0;
if rng_bounce < r_unpol {
let new_k =
apply_partial_reflect_bounce(geo, r_unpol, k_hat, normal, state.stokes, state.path_pdf);
let new_s = poynting_dir_for_mode(ctx, geo, new_k, inside_gem, is_extraordinary);
(new_k, new_s, inside_gem, None, None)
} else {
let selection = RefractSelection {
use_extraordinary,
entry_mode_azimuth2,
};
let outcome =
apply_refract_bounce(bctx, r_unpol, ray, inside_gem, selection, state, exit_event);
(
outcome.new_k,
outcome.new_s,
!inside_gem,
outcome.is_extraordinary_update,
None,
)
}
}