use macaw::Ray3;
use macaw::Vec3;
use std::cmp::Ordering;
use std::ops::RangeInclusive;
pub struct Options {
max_steps: usize,
step_constant: f32,
}
impl Default for Options {
fn default() -> Self {
Self {
max_steps: 1024,
step_constant: 1.0,
}
}
}
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ClosestHit {
pub t: f32,
pub pos: Vec3,
pub dist: f32,
pub is_hit: bool,
}
impl Default for ClosestHit {
fn default() -> Self {
Self::miss()
}
}
impl ClosestHit {
pub fn miss() -> Self {
Self {
t: f32::INFINITY,
pos: Vec3::splat(f32::NAN),
dist: f32::INFINITY,
is_hit: false,
}
}
pub fn angle_distance(&self) -> f32 {
if self.t <= self.dist {
f32::INFINITY } else {
self.dist / self.t
}
}
}
impl std::cmp::PartialOrd for ClosestHit {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
match (self.is_hit, other.is_hit) {
(true, false) => Some(Ordering::Less), (false, true) => Some(Ordering::Greater), (true, true) => self.t.partial_cmp(&other.t), (false, false) => self.angle_distance().partial_cmp(&other.angle_distance()), }
}
}
pub fn trace(
mut sd: impl FnMut(Vec3) -> f32,
ray: Ray3,
t_range: RangeInclusive<f32>,
opt: &Options,
) -> ClosestHit {
let mut t = *t_range.start();
let mut closest_angle_distance = f32::INFINITY;
let mut closest = ClosestHit::miss();
for _ in 0..opt.max_steps {
let pos = ray.point_along(t);
let dist = sd(pos);
if dist <= 0.001 * t {
return ClosestHit {
t,
pos,
dist,
is_hit: true,
};
} else {
if t > 0.0 {
let angle_distance = dist / t;
if angle_distance < closest_angle_distance {
closest_angle_distance = angle_distance;
closest = ClosestHit {
t,
pos,
dist,
is_hit: false,
};
}
}
t += dist * opt.step_constant;
if t >= *t_range.end() {
return closest;
}
}
}
closest
}