use crate::{RenderHit, RenderRayWalk};
use voxrender::{RenderRay, RenderScene, Result};
pub fn cast_ray(
scene: &RenderScene,
ray: &RenderRay,
max_distance: f64,
) -> Result<Option<RenderHit>> {
let hit = RenderRayWalk::from_ray(scene, ray)?.next();
Ok(hit.filter(|hit| hit.distance <= max_distance))
}
#[cfg(test)]
mod tests {
use crate::cast_ray;
use std::f64::consts::PI;
use ty_math::{TyQuaternionF64, TyTransformF64, TyVector3F64};
use voxrender::{RenderRay, fixtures::cells_scene};
fn ray(origin: [f64; 3], direction: [f64; 3]) -> RenderRay {
RenderRay {
origin: TyVector3F64::from_array(origin),
direction: TyVector3F64::from_array(direction).normalize(),
}
}
#[test]
fn a_ray_meets_each_face_of_a_cube_where_it_lands() {
let scene = cells_scene([1, 1, 1], &[([0, 0, 0], 0)], &[TyTransformF64::IDENTITY]);
for axis in 0..3 {
for sign in [-1i32, 1] {
let mut origin = [0.25; 3];
origin[axis] = if sign > 0 { 4.0 } else { -3.0 };
let mut direction = [0.0; 3];
direction[axis] = -f64::from(sign);
let hit = cast_ray(&scene, &ray(origin, direction), f64::INFINITY)
.unwrap()
.unwrap();
assert_eq!(hit.face.d, axis);
assert_eq!(hit.face.sign, sign);
assert_eq!(hit.face.s, 0);
assert_eq!(hit.along, [0.25, 0.25]);
assert_eq!(hit.distance, 3.0);
}
}
}
#[test]
fn a_ray_beside_the_cube_misses() {
let scene = cells_scene([1, 1, 1], &[([0, 0, 0], 0)], &[TyTransformF64::IDENTITY]);
assert_eq!(
cast_ray(
&scene,
&ray([0.5, 1.5, 4.0], [0.0, 0.0, -1.0]),
f64::INFINITY
)
.unwrap(),
None
);
assert_eq!(
cast_ray(
&scene,
&ray([0.5, 0.5, 4.0], [0.0, 0.0, 1.0]),
f64::INFINITY
)
.unwrap(),
None
);
assert_eq!(
cast_ray(&scene, &ray([0.5, 0.5, 4.0], [0.0, 0.0, -1.0]), 2.0).unwrap(),
None
);
}
#[test]
fn a_ray_from_inside_a_voxel_leaves_its_material_first() {
let scene = cells_scene(
[2, 1, 1],
&[([0, 0, 0], 0), ([1, 0, 0], 1)],
&[TyTransformF64::IDENTITY],
);
let hit = cast_ray(
&scene,
&ray([0.5, 0.5, 0.5], [1.0, 0.0, 0.0]),
f64::INFINITY,
)
.unwrap()
.unwrap();
assert_eq!(hit.face.d, 0);
assert_eq!(hit.face.sign, -1);
assert_eq!(hit.face.s, 1);
assert_eq!(hit.distance, 0.5);
assert_eq!(
cast_ray(
&scene,
&ray([0.5, 0.5, 0.5], [-1.0, 0.0, 0.0]),
f64::INFINITY
)
.unwrap(),
None
);
}
#[test]
fn the_nearer_placement_wins_and_the_transform_applies() {
let far = TyTransformF64::from_translation(TyVector3F64::new(0.0, 0.0, -5.0));
let stacked = cells_scene(
[1, 1, 1],
&[([0, 0, 0], 0)],
&[far, TyTransformF64::IDENTITY],
);
let hit = cast_ray(
&stacked,
&ray([0.5, 0.5, 10.0], [0.0, 0.0, -1.0]),
f64::INFINITY,
)
.unwrap()
.unwrap();
assert_eq!(hit.distance, 9.0);
assert_eq!(
hit.placement_id,
stacked.iter_placements().nth(1).unwrap().0
);
let turned = TyTransformF64::new(
TyVector3F64::new(10.0, 0.0, 0.0),
TyQuaternionF64::from_axis_angle(TyVector3F64::Y, PI / 2.0),
TyVector3F64::splat(2.0),
);
let turned_scene = cells_scene([1, 1, 1], &[([0, 0, 0], 0)], &[turned]);
let hit = cast_ray(
&turned_scene,
&ray([11.0, 1.0, 5.0], [0.0, 0.0, -1.0]),
f64::INFINITY,
)
.unwrap()
.unwrap();
assert!((hit.distance - 5.0).abs() < 1e-9);
assert_eq!(hit.face.d, 0);
assert_eq!(hit.face.sign, -1);
}
}