vanrijn/src/raycasting/mod.rs

172 lines
5.3 KiB
Rust

use crate::math::{Affine3,Vec3};
use super::materials::Material;
use std::sync::Arc;
pub mod sphere;
pub use sphere::Sphere;
pub mod plane;
pub use plane::Plane;
pub mod triangle;
pub use triangle::Triangle;
pub mod axis_aligned_bounding_box;
pub use axis_aligned_bounding_box::BoundingBox;
pub mod bounding_volume_hierarchy;
pub use bounding_volume_hierarchy::BoundingVolumeHierarchy;
pub mod vec_aggregate;
/// A ray, consisting or a start point and direction
///
/// This is the basic ray struct used to define things like a line-of-sight
/// going out from the camera of a reflection from a surface.
#[derive(Clone, Debug)]
pub struct Ray {
/// The start point of the ray
pub origin: Vec3<f64>,
/// The direction the ray goes in.
///
/// This vector should always be kept normalized
pub direction: Vec3<f64>,
}
impl Ray {
/// Create a new ray
pub fn new(origin: Vec3<f64>, direction: Vec3<f64>) -> Ray {
Ray {
origin,
direction: direction.normalize(),
}
}
/// Return the point on the ray that is `t` units from the start
pub fn point_at(&self, t: f64) -> Vec3<f64> {
self.origin + self.direction * t
}
/// Create a new ray by moving the original ray along it's direction by `amount`
///
/// `amount` is normally a very small number. This function is useful for ensuring
/// that rounding-errors don;t cause a reflection ray doesn't intersect with the point
/// it's reflected from.
pub fn bias(&self, amount: f64) -> Ray {
Ray::new(self.origin + self.direction * amount, self.direction)
}
}
/// Information about a ray-primitive intersection.
///
/// This struct is returned by [intersect()](Intersect::intersect) and contatins all the
/// information needed to evaluate the rendering function for that intersection.
#[derive(Debug)]
pub struct IntersectionInfo {
/// The distance between the ray origin and the intersection point
pub distance: f64,
/// The intersection point
pub location: Vec3<f64>,
/// The surface normal at the intersection point
pub normal: Vec3<f64>,
/// The surface tangent at the intersection point
///
/// Which surface tangent direction returned is dependent on the [Primitive](Primitive)
/// but should generally be smooth over any given surface
pub tangent: Vec3<f64>,
/// Another surface tangent, perpendicular to `tangent`
///
/// The cross product or `normal` and `tangent`
pub cotangent: Vec3<f64>,
/// The direction from the intersection point back towards the ray
///
/// Equal to `-ray.direction`
pub retro: Vec3<f64>,
/// The [Material](crate::materials::Material) which describes the optical
/// properties of the intersected surface
pub material: Arc<dyn Material>,
}
/// A geometric object that has a [Material](crate::materials::Material) and can be
/// intersected with a [Ray](Ray)
pub trait Intersect: Send + Sync {
/// Test if the ray intersects the object, and return information about the object and intersection.
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo>;
}
/// A geometric object that can be intersected with a ray
///
/// This is useful for objects that don't have materials (such as [BoundingBox](BoundingBox))
/// and as a (possibly) faster alternative to [Intersect](Intersect) when only a simple
/// intersection test is needed.
pub trait IntersectP: Send + Sync {
/// Test if the ray intersects the object, without calculating any extra information.
fn intersect(&self, ray: &Ray) -> bool;
}
/// Any geometric object for which a [BoundingBox](BoundingBox) can be calculated
pub trait HasBoundingBox: Send + Sync {
/// The axis-aligned bounding box of the object
///
/// The object must fit entirely inside this box.
fn bounding_box(&self) -> BoundingBox;
}
/// A basic geometric primitive such as a sphere or a triangle
pub trait Primitive: Intersect + HasBoundingBox {
// / Create a new object by applying the transformation to this object.
fn transform(&self, transformation: &Affine3<f64>) -> Box<dyn Primitive>;
}
#[cfg(test)]
mod tests {
use quickcheck_macros::quickcheck;
use super::*;
use quickcheck::{Arbitrary, Gen};
impl Arbitrary for Ray {
fn arbitrary<G: Gen>(g: &mut G) -> Ray {
let origin = <Vec3<f64> as Arbitrary>::arbitrary(g);
let direction = <Vec3<f64> as Arbitrary>::arbitrary(g);
return Ray::new(origin, direction);
}
}
#[quickcheck]
fn t0_is_origin(ray: Ray) -> bool {
ray.point_at(0.0) == ray.origin
}
#[quickcheck]
fn t1_is_origin_plus_direction(ray: Ray) -> bool {
ray.point_at(1.0) == ray.origin + ray.direction
}
#[quickcheck]
fn points_are_colinear(ray: Ray, t1: f64, t2: f64, t3: f64) -> bool {
let p1 = ray.point_at(t1);
let p2 = ray.point_at(t2);
let p3 = ray.point_at(t3);
let epsilon = [t1, t2, t3, ray.origin[0], ray.origin[1], ray.origin[2]]
.iter()
.fold(0.0f64, |a, &b| a.max(b.abs()))
* std::f64::EPSILON
* 256.0f64;
(p2 - p1).cross(&(p3 - p2)).norm() < epsilon
}
#[quickcheck]
fn t_is_distance(ray: Ray, t: f64) -> bool {
(ray.point_at(t) - ray.origin).norm() - t.abs() < 0.0000000001
}
}