use super::*;
fn vector3d_min(a: Vector3D, b: Vector3D) -> Vector3D {
Vector3D::new(
a.get_x().min(b.get_x()),
a.get_y().min(b.get_y()),
a.get_z().min(b.get_z()),
)
}
fn vector3d_max(a: Vector3D, b: Vector3D) -> Vector3D {
Vector3D::new(
a.get_x().max(b.get_x()),
a.get_y().max(b.get_y()),
a.get_z().max(b.get_z()),
)
}
fn slab_interval(
origin_component: f64,
dir_component: f64,
lower: f64,
upper: f64,
) -> Option<(f64, f64)> {
if dir_component.abs() < RAYTRACE_TRIANGLE_EPSILON {
if origin_component < lower || origin_component > upper {
return None;
}
return Some((f64::NEG_INFINITY, f64::INFINITY));
}
let inverse: f64 = 1.0 / dir_component;
let t0: f64 = (lower - origin_component) * inverse;
let t1: f64 = (upper - origin_component) * inverse;
Some((t0.min(t1), t0.max(t1)))
}
fn ray_segment_overlaps_aabb(
origin: Vector3D,
dir: Vector3D,
lower: Vector3D,
upper: Vector3D,
t_min: f64,
t_max: f64,
) -> bool {
let x: Option<(f64, f64)> =
slab_interval(origin.get_x(), dir.get_x(), lower.get_x(), upper.get_x());
let y: Option<(f64, f64)> =
slab_interval(origin.get_y(), dir.get_y(), lower.get_y(), upper.get_y());
let z: Option<(f64, f64)> =
slab_interval(origin.get_z(), dir.get_z(), lower.get_z(), upper.get_z());
match (x, y, z) {
(Some((x0, x1)), Some((y0, y1)), Some((z0, z1))) => {
let t_enter: f64 = x0.max(y0).max(z0);
let t_exit: f64 = x1.min(y1).min(z1);
t_enter <= t_exit && t_exit >= t_min && t_enter <= t_max
}
_ => false,
}
}
pub fn intersect_triangle(
origin: Vector3D,
dir: Vector3D,
v0: Vector3D,
v1: Vector3D,
v2: Vector3D,
) -> Option<(f64, Vector3D)> {
let edge1: Vector3D = v1 - v0;
let edge2: Vector3D = v2 - v0;
let pvec: Vector3D = dir.cross(edge2);
let det: f64 = edge1.dot(pvec);
if det.abs() < RAYTRACE_TRIANGLE_EPSILON {
return None;
}
let inverse_det: f64 = 1.0 / det;
let tvec: Vector3D = origin - v0;
let u: f64 = tvec.dot(pvec) * inverse_det;
if u < 0.0 || u > 1.0 {
return None;
}
let qvec: Vector3D = tvec.cross(edge1);
let v: f64 = dir.dot(qvec) * inverse_det;
if v < 0.0 || u + v > 1.0 {
return None;
}
let t: f64 = edge2.dot(qvec) * inverse_det;
let geometric: Vector3D = edge1.cross(edge2).normalized();
let normal: Vector3D = if geometric.dot(dir) > 0.0 {
-geometric
} else {
geometric
};
Some((t, normal))
}
fn occluder_aabb_extents(occluder: &Occluder) -> (Vector3D, Vector3D) {
let (mn, mx): (Vector3D, Vector3D) = match occluder.get_kind() {
OccluderKind::Aabb => (occluder.get_center(), occluder.get_extent()),
OccluderKind::Sphere => {
let center: Vector3D = occluder.get_center();
let radius: f64 = occluder.get_extent().get_x();
let r: Vector3D = Vector3D::new(radius, radius, radius);
(center - r, center + r)
}
OccluderKind::Triangle => {
let [v0, v1, v2]: [Vector3D; 3] = occluder.get_vertices();
let lo: Vector3D = vector3d_min(v0, vector3d_min(v1, v2));
let hi: Vector3D = vector3d_max(v0, vector3d_max(v1, v2));
(lo, hi)
}
};
(vector3d_min(mn, mx), vector3d_max(mn, mx))
}
pub(crate) fn collect_occluder_points(occluders: &[Occluder]) -> Vec<(Vector3D, f64)> {
let mut out: Vec<(Vector3D, f64)> = Vec::new();
for occ in occluders.iter() {
let (mn, mx): (Vector3D, Vector3D) = occluder_aabb_extents(occ);
let cx: f64 = (mn.get_x() + mx.get_x()) * 0.5;
let cy: f64 = (mn.get_y() + mx.get_y()) * 0.5;
let cz: f64 = (mn.get_z() + mx.get_z()) * 0.5;
let ex: f64 = (mx.get_x() - mn.get_x()) * 0.5;
let ey: f64 = (mx.get_y() - mn.get_y()) * 0.5;
let ez: f64 = (mx.get_z() - mn.get_z()) * 0.5;
let r: f64 = (ex * ex + ey * ey + ez * ez).sqrt();
out.push((Vector3D::new(cx, cy, cz), r));
}
out
}
pub(crate) fn closest_hit_indexed(
ray: &Ray,
occluders: &[Occluder],
) -> Option<(usize, f64, Vector3D, Vector3D)> {
let origin: Vector3D = ray.get_origin();
let dir: Vector3D = ray.get_direction();
let t_min: f64 = ray.get_t_min();
let t_max: f64 = ray.get_t_max();
let mut best: Option<(usize, f64, Vector3D, Vector3D)> = None;
for (index, occ) in occluders.iter().enumerate() {
let (bound_min, bound_max): (Vector3D, Vector3D) = occluder_aabb_extents(occ);
if !ray_segment_overlaps_aabb(origin, dir, bound_min, bound_max, t_min, t_max) {
continue;
}
let candidate: Option<(f64, Vector3D)> = match occ.get_kind() {
OccluderKind::Sphere => {
let center: Vector3D = occ.get_center();
let radius: f64 = occ.get_extent().get_x();
match ray_sphere_intersect(origin, dir, center, radius) {
Some((t, n)) if t >= t_min && t <= t_max => Some((t, n)),
_ => None,
}
}
OccluderKind::Aabb => match ray_aabb_intersect(origin, dir, bound_min, bound_max) {
Some((t_near, _t_far, n)) if t_near >= t_min && t_near <= t_max => {
Some((t_near, n))
}
_ => None,
},
OccluderKind::Triangle => {
let [v0, v1, v2]: [Vector3D; 3] = occ.get_vertices();
ray.intersect_triangle(v0, v1, v2)
}
};
if let Some((t, n)) = candidate {
let keep_previous: bool = matches!(&best, Some(previous) if previous.1 <= t);
if !keep_previous {
let hit_pos: Vector3D = origin + dir.scaled(t);
best = Some((index, t, hit_pos, n));
}
}
}
best
}
fn bounce_ray(ray: &Ray, position: Vector3D, normal: Vector3D) -> Ray {
let dir: Vector3D = ray.get_direction();
let dot: f64 = dir.dot(normal);
let reflected_dir: Vector3D = dir - normal.scaled(2.0 * dot);
Ray {
origin: position,
direction: reflected_dir,
t_min: RAYTRACE_DEFAULT_T_MIN,
t_max: RAYTRACE_DEFAULT_T_MAX,
depth: ray.get_depth() + 1,
}
}
pub(crate) fn trace_bounces(
ray: Ray,
occluders: &[Occluder],
shadow_points: &[(Vector3D, f64)],
lights: &LightingUniforms,
max_bounces: u32,
) -> Vector3D {
let ambient: Vector3D = lights.get_ambient();
let mut color: Vector3D = Vector3D::zero();
let mut throughput: f64 = 1.0;
let mut current: Ray = ray;
loop {
let (index, _t, position, normal): (usize, f64, Vector3D, Vector3D) =
match closest_hit_indexed(¤t, occluders) {
None => {
color += ambient.scaled(throughput);
break;
}
Some(hit) => hit,
};
let material: &Material = occluders[index].get_material();
color += lights
.shade(position, normal, material, shadow_points)
.scaled(throughput);
let spec: f64 = material.get_specular();
if current.get_depth() >= max_bounces || spec <= EPSILON {
break;
}
throughput *= spec;
current = bounce_ray(¤t, position, normal);
}
color
}