saft 0.32.0

Signed distance field function compiler/interpreter/discretizer/mesher
Documentation
use crate::Ray3;
use crate::Vec3;
use std::cmp::Ordering;
use std::ops::RangeInclusive;

pub struct Options {
    /// Don't take more steps than this
    max_steps: usize,

    /// 1.0. Set to lower if your field is unreliable (i.e. underestimates distances).
    step_constant: f32,
}

impl Default for Options {
    fn default() -> Self {
        Self {
            max_steps: 1024,
            step_constant: 1.0,
        }
    }
}

/// A point along the ray, and some info about it.
/// Often this is the point along a march that was (approximately) closest to a surface.
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct ClosestHit {
    /// Distance along ray.
    pub t: f32,
    /// Point in world.
    pub pos: Vec3,
    /// Distance to surface.
    pub dist: f32,
    /// Is this point considered a hit on the surface?
    /// If false, this point is the closest point we've found to a surface.
    pub is_hit: bool,
}

impl Default for ClosestHit {
    fn default() -> Self {
        ClosestHit::miss()
    }
}

impl ClosestHit {
    pub fn miss() -> Self {
        Self {
            t: f32::INFINITY,
            pos: Vec3::splat(f32::NAN),
            dist: f32::INFINITY,
            is_hit: false,
        }
    }

    /// How close was the hit, as seen from the ray origin?
    pub fn angle_distance(&self) -> f32 {
        if self.t <= self.dist {
            f32::INFINITY // angle doesn't make sense
        } else {
            self.dist / self.t
        }
    }
}

/// Less means earlier or closer hit.
impl std::cmp::PartialOrd for ClosestHit {
    fn partial_cmp(&self, other: &ClosestHit) -> Option<Ordering> {
        match (self.is_hit, other.is_hit) {
            (true, false) => Some(Ordering::Less),    // hits before misses
            (false, true) => Some(Ordering::Greater), // misses after hits
            (true, true) => self.t.partial_cmp(&other.t), // both hits: first hit is first
            (false, false) => self.angle_distance().partial_cmp(&other.angle_distance()), // both missed: closest is first
        }
    }
}

/// Marches a ray from `t_range.start()` until `t_range.end()`,
/// returning the first hit or (in case of no hit) the point that were closest to the surface.
///
/// `sd` is a signed distance field that should never underestimate the distance to the surface.
/// The surface is where the signed distance is zero.
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 {
                // `angle_distance`: dist as viewed from ray origin
                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;
            }
        }
    }

    // TODO: communicate that max_steps has been reached,
    // i.e. that the march was prematurely aborted
    closest
}