mod march;
pub use march::{
raymarch, raymarch_batch, raymarch_batch_parallel, raymarch_with_config, render_depth,
render_normals, RaymarchConfig, RaymarchResult,
};
pub use march::{
raymarch_compiled, raymarch_compiled_batch_parallel, raymarch_compiled_with_config,
render_depth_compiled, render_normals_compiled,
};
pub use march::{raymarch_simd_8, render_depth_compiled_simd};
#[cfg(feature = "jit")]
pub use march::{
raymarch_jit, raymarch_jit_batch_parallel, raymarch_jit_with_config, render_depth_jit,
};
#[cfg(feature = "jit")]
pub use march::{raymarch_jit_simd_8, render_depth_jit_simd};
use crate::compiled::{eval_compiled, CompiledSdf};
use crate::eval::eval;
use crate::types::{Hit, Ray};
use crate::SdfNode;
use glam::Vec3;
#[inline(always)]
pub fn raycast(node: &SdfNode, ray: Ray, max_distance: f32) -> Option<Hit> {
raymarch(node, ray.origin, ray.direction, max_distance)
}
pub fn raycast_batch(node: &SdfNode, rays: &[Ray], max_distance: f32) -> Vec<Option<Hit>> {
raymarch_batch_parallel(node, rays, max_distance)
}
#[inline(always)]
pub fn ambient_occlusion(
node: &SdfNode,
point: Vec3,
normal: Vec3,
samples: u32,
max_distance: f32,
) -> f32 {
let mut occlusion = 0.0;
let inv_samples = 1.0 / samples as f32;
let step = max_distance * inv_samples;
for i in 1..=samples {
let t = i as f32 * step;
let sample_point = point + normal * t;
let d = eval(node, sample_point);
let contribution = (t - d.max(0.0)) / t;
occlusion += contribution;
}
(1.0 - occlusion * inv_samples).clamp(0.0, 1.0)
}
#[inline(always)]
pub fn soft_shadow(
node: &SdfNode,
origin: Vec3,
direction: Vec3,
min_t: f32,
max_t: f32,
k: f32,
) -> f32 {
let mut res: f32 = 1.0;
let mut t = min_t;
let mut ph = 1e10;
const MAX_STEPS: u32 = 64;
for _ in 0..MAX_STEPS {
if t >= max_t {
break;
}
let h = eval(node, origin + direction * t);
if h < 0.001 {
return 0.0;
}
let y = h * h / (2.0 * ph);
let d = h.mul_add(h, -(y * y)).sqrt();
res = res.min(k * d / (t - y).max(0.0));
ph = h;
t += h;
}
res.clamp(0.0, 1.0)
}
#[inline(always)]
pub fn hard_shadow(node: &SdfNode, origin: Vec3, direction: Vec3, min_t: f32, max_t: f32) -> bool {
const EPSILON: f32 = 0.001;
const MAX_STEPS: u32 = 64;
let mut t = min_t;
for _ in 0..MAX_STEPS {
if t >= max_t {
return false;
}
let h = eval(node, origin + direction * t);
if h < EPSILON {
return true;
}
t += h;
}
false
}
#[inline(always)]
pub fn ambient_occlusion_compiled(
sdf: &CompiledSdf,
point: Vec3,
normal: Vec3,
samples: u32,
max_distance: f32,
) -> f32 {
let mut occlusion = 0.0;
let inv_samples = 1.0 / samples as f32;
let step = max_distance * inv_samples;
for i in 1..=samples {
let t = i as f32 * step;
let sample_point = point + normal * t;
let d = eval_compiled(sdf, sample_point);
let contribution = (t - d.max(0.0)) / t;
occlusion += contribution;
}
(1.0 - occlusion * inv_samples).clamp(0.0, 1.0)
}
#[inline(always)]
pub fn soft_shadow_compiled(
sdf: &CompiledSdf,
origin: Vec3,
direction: Vec3,
min_t: f32,
max_t: f32,
k: f32,
) -> f32 {
let mut res: f32 = 1.0;
let mut t = min_t;
let mut ph = 1e10;
const MAX_STEPS: u32 = 64;
for _ in 0..MAX_STEPS {
if t >= max_t {
break;
}
let h = eval_compiled(sdf, origin + direction * t);
if h < 0.001 {
return 0.0;
}
let y = h * h / (2.0 * ph);
let d = h.mul_add(h, -(y * y)).sqrt();
res = res.min(k * d / (t - y).max(0.0));
ph = h;
t += h;
}
res.clamp(0.0, 1.0)
}
#[inline(always)]
pub fn hard_shadow_compiled(
sdf: &CompiledSdf,
origin: Vec3,
direction: Vec3,
min_t: f32,
max_t: f32,
) -> bool {
const EPSILON: f32 = 0.001;
const MAX_STEPS: u32 = 64;
let mut t = min_t;
for _ in 0..MAX_STEPS {
if t >= max_t {
return false;
}
let h = eval_compiled(sdf, origin + direction * t);
if h < EPSILON {
return true;
}
t += h;
}
false
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_raycast_hit() {
let sphere = SdfNode::sphere(1.0);
let ray = Ray::new(Vec3::new(-5.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0));
let hit = raycast(&sphere, ray, 10.0);
assert!(hit.is_some());
let hit = hit.unwrap();
assert!((hit.distance - 4.0).abs() < 0.01);
}
#[test]
fn test_raycast_miss() {
let sphere = SdfNode::sphere(1.0);
let ray = Ray::new(Vec3::new(-5.0, 5.0, 0.0), Vec3::new(1.0, 0.0, 0.0));
let hit = raycast(&sphere, ray, 10.0);
assert!(hit.is_none());
}
#[test]
fn test_ambient_occlusion() {
let sphere = SdfNode::sphere(1.0);
let point = Vec3::new(1.0, 0.0, 0.0);
let normal = Vec3::new(1.0, 0.0, 0.0);
let ao = ambient_occlusion(&sphere, point, normal, 5, 0.5);
assert!(ao > 0.5);
}
#[test]
fn test_soft_shadow_no_occluder() {
let sphere = SdfNode::sphere(1.0).translate(0.0, 0.0, 5.0);
let origin = Vec3::new(0.0, 0.0, 0.0);
let to_light = Vec3::new(0.0, 1.0, 0.0).normalize();
let shadow = soft_shadow(&sphere, origin, to_light, 0.01, 10.0, 8.0);
assert!((shadow - 1.0).abs() < 0.1);
}
#[test]
fn test_soft_shadow_with_occluder() {
let sphere = SdfNode::sphere(0.5).translate(0.0, 2.0, 0.0);
let origin = Vec3::new(0.0, 0.0, 0.0);
let to_light = Vec3::new(0.0, 1.0, 0.0).normalize();
let shadow = soft_shadow(&sphere, origin, to_light, 0.01, 10.0, 8.0);
assert!(shadow < 0.5);
}
#[test]
fn test_hard_shadow() {
let sphere = SdfNode::sphere(0.5).translate(0.0, 2.0, 0.0);
let origin = Vec3::new(0.0, 0.0, 0.0);
let to_light = Vec3::new(0.0, 1.0, 0.0).normalize();
let in_shadow = hard_shadow(&sphere, origin, to_light, 0.01, 10.0);
assert!(in_shadow);
let to_side = Vec3::new(1.0, 0.0, 0.0).normalize();
let not_in_shadow = hard_shadow(&sphere, origin, to_side, 0.01, 10.0);
assert!(!not_in_shadow);
}
#[test]
fn test_hard_shadow_no_occluder() {
let sphere = SdfNode::sphere(1.0).translate(0.0, 0.0, 5.0);
let origin = Vec3::new(0.0, 0.0, 0.0);
let to_light = Vec3::new(0.0, 1.0, 0.0).normalize();
let in_shadow = hard_shadow(&sphere, origin, to_light, 0.01, 10.0);
assert!(!in_shadow);
}
#[test]
fn test_ao_compiled() {
let sphere = SdfNode::sphere(1.0);
let compiled = CompiledSdf::compile(&sphere);
let point = Vec3::new(1.0, 0.0, 0.0);
let normal = Vec3::new(1.0, 0.0, 0.0);
let ao = ambient_occlusion_compiled(&compiled, point, normal, 5, 0.5);
assert!(ao > 0.5);
let ao_interp = ambient_occlusion(&sphere, point, normal, 5, 0.5);
assert!((ao - ao_interp).abs() < 0.01);
}
#[test]
fn test_soft_shadow_compiled() {
let sphere = SdfNode::sphere(0.5).translate(0.0, 2.0, 0.0);
let compiled = CompiledSdf::compile(&sphere);
let origin = Vec3::new(0.0, 0.0, 0.0);
let to_light = Vec3::new(0.0, 1.0, 0.0).normalize();
let shadow = soft_shadow_compiled(&compiled, origin, to_light, 0.01, 10.0, 8.0);
assert!(shadow < 0.5);
let shadow_interp = soft_shadow(&sphere, origin, to_light, 0.01, 10.0, 8.0);
assert!((shadow - shadow_interp).abs() < 0.01);
}
#[test]
fn test_hard_shadow_compiled() {
let sphere = SdfNode::sphere(0.5).translate(0.0, 2.0, 0.0);
let compiled = CompiledSdf::compile(&sphere);
let origin = Vec3::new(0.0, 0.0, 0.0);
let to_light = Vec3::new(0.0, 1.0, 0.0).normalize();
let in_shadow = hard_shadow_compiled(&compiled, origin, to_light, 0.01, 10.0);
assert!(in_shadow);
let to_side = Vec3::new(1.0, 0.0, 0.0).normalize();
let not_in_shadow = hard_shadow_compiled(&compiled, origin, to_side, 0.01, 10.0);
assert!(!not_in_shadow);
}
}