//! Utilities for handling floating point precision issues //! //! This is based on the work in section 3.9 of "Physically Based Rendering: //! From Theory to Implementation" 3rd edition by Pharr et al. use crate::math::{dot, Normal, Point, Vector}; #[inline(always)] pub fn fp_gamma(n: u32) -> f32 { use std::f32::EPSILON; let e = EPSILON * 0.5; (e * n as f32) / (1.0 - (e * n as f32)) } pub fn increment_ulp(v: f32) -> f32 { if v.is_finite() { if v > 0.0 { f32::from_bits(v.to_bits() + 1) } else if v < -0.0 { f32::from_bits(v.to_bits() - 1) } else { f32::from_bits(0x00_00_00_01) } } else { // Infinity or NaN. v } } pub fn decrement_ulp(v: f32) -> f32 { if v.is_finite() { if v > 0.0 { f32::from_bits(v.to_bits() - 1) } else if v < -0.0 { f32::from_bits(v.to_bits() + 1) } else { f32::from_bits(0x80_00_00_01) } } else { // Infinity or NaN. v } } pub fn robust_ray_origin(pos: Point, pos_err: f32, nor: Normal, ray_dir: Vector) -> Point { // Get surface normal pointing in the same // direction as ray_dir. let nor = { let nor = nor.into_vector(); if dot(nor, ray_dir) >= 0.0 { nor } else { -nor } }; // Calculate offset point let d = dot(nor.abs(), Vector::new(pos_err, pos_err, pos_err)); let offset = nor * d; let p = pos + offset; // Calculate ulp offsets let x = if nor.x() >= 0.0 { increment_ulp(p.x()) } else { decrement_ulp(p.x()) }; let y = if nor.y() >= 0.0 { increment_ulp(p.y()) } else { decrement_ulp(p.y()) }; let z = if nor.z() >= 0.0 { increment_ulp(p.z()) } else { decrement_ulp(p.z()) }; Point::new(x, y, z) } #[cfg(test)] mod tests { use super::*; #[test] fn inc_ulp() { assert!(increment_ulp(1.0) > 1.0); assert!(increment_ulp(-1.0) > -1.0); } #[test] fn dec_ulp() { assert!(decrement_ulp(1.0) < 1.0); assert!(decrement_ulp(-1.0) < -1.0); } #[test] fn inc_ulp_zero() { assert!(increment_ulp(0.0) > 0.0); assert!(increment_ulp(0.0) > -0.0); assert!(increment_ulp(-0.0) > 0.0); assert!(increment_ulp(-0.0) > -0.0); } #[test] fn dec_ulp_zero() { assert!(decrement_ulp(0.0) < 0.0); assert!(decrement_ulp(0.0) < -0.0); assert!(decrement_ulp(-0.0) < 0.0); assert!(decrement_ulp(-0.0) < -0.0); } #[test] fn inc_dec_ulp() { assert_eq!(decrement_ulp(increment_ulp(1.0)), 1.0); assert_eq!(decrement_ulp(increment_ulp(-1.0)), -1.0); assert_eq!(decrement_ulp(increment_ulp(1.2)), 1.2); assert_eq!(decrement_ulp(increment_ulp(-1.2)), -1.2); } #[test] fn dec_inc_ulp() { assert_eq!(increment_ulp(decrement_ulp(1.0)), 1.0); assert_eq!(increment_ulp(decrement_ulp(-1.0)), -1.0); assert_eq!(increment_ulp(decrement_ulp(1.2)), 1.2); assert_eq!(increment_ulp(decrement_ulp(-1.2)), -1.2); } }