It looks like I messed up the preference for the HitRecords. The geometry bounces correctly, but the record that sticks is not necessarily the one closest to the camera.
234 lines
7.5 KiB
Rust
234 lines
7.5 KiB
Rust
|
|
use crate::primitives::{Vec3, Ray};
|
|
|
|
use rand::Rng;
|
|
use rand::rngs::SmallRng;
|
|
use rand::distributions::Uniform;
|
|
|
|
pub struct HitRecord{
|
|
pub p: Vec3,
|
|
pub normal: Vec3,
|
|
pub material: Material,
|
|
pub t: f32,
|
|
pub front_face: bool,
|
|
}
|
|
|
|
impl HitRecord{
|
|
pub fn set_face_normal(&mut self, r: Ray, outward_normal: Vec3) -> (){
|
|
self.front_face = Vec3::dot(r.dir, outward_normal) < 0.0;
|
|
self.normal = if self.front_face { outward_normal } else { -outward_normal };
|
|
}
|
|
}
|
|
|
|
#[derive (Clone)]
|
|
pub enum Hittable {
|
|
Sphere { center: Vec3, radius: f32, material: Material },
|
|
HittableList { hittables: Vec<Hittable> }
|
|
}
|
|
|
|
impl Hittable {
|
|
pub fn hit(&self, r: Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
|
match self {
|
|
Hittable::HittableList { hittables } => {
|
|
let mut might_return: Option<HitRecord> = None;
|
|
let mut hit_anything = false;
|
|
|
|
for item in hittables {
|
|
if let Some(record) = item.hit(r, t_min, t_max){
|
|
hit_anything = true;
|
|
might_return = Some(record);
|
|
}
|
|
}
|
|
if hit_anything{
|
|
return might_return;
|
|
} else { return None; }
|
|
}
|
|
|
|
Hittable::Sphere { center, radius, material } => {
|
|
let oc = r.orig - *center;
|
|
let a = r.dir.length_squared();
|
|
let half_b = Vec3::dot(oc, r.dir);
|
|
let c = oc.length_squared() - radius * radius;
|
|
let discriminant = half_b*half_b - a*c;
|
|
|
|
if discriminant < 0.0 {
|
|
return None;
|
|
}
|
|
let sqrtd = discriminant.sqrt();
|
|
|
|
// nearest root that lies within tolerance
|
|
let mut root = (-half_b - sqrtd) / a;
|
|
if root < t_min || root > t_max {
|
|
root = (-half_b + sqrtd) / a;
|
|
if root < t_min || root > t_max {
|
|
return None;
|
|
}
|
|
}
|
|
let mut record = HitRecord{
|
|
p: r.at(root),
|
|
normal: (r.at(root) - *center) / *radius,
|
|
material: *material,
|
|
t: root,
|
|
front_face: false,
|
|
};
|
|
let outward_normal = (record.p - *center) / *radius;
|
|
record.set_face_normal(r, outward_normal);
|
|
Some(record)
|
|
}
|
|
}
|
|
}
|
|
pub fn push(&mut self, item: Hittable) {
|
|
if let Hittable::HittableList { hittables } = self {
|
|
hittables.push(item);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#[derive(Copy, Clone, Debug)]
|
|
pub enum Material{
|
|
Lambertian { albedo: Vec3 },
|
|
Metal { albedo:Vec3, fuzz: f32 },
|
|
Dielectric { index_refraction: f32 },
|
|
}
|
|
|
|
impl Material {
|
|
pub fn scatter(
|
|
&self,
|
|
ray_in: Ray,
|
|
rec: &HitRecord,
|
|
attenuation: &mut Vec3,
|
|
scattered: &mut Ray,
|
|
srng: &mut SmallRng,
|
|
) -> bool {
|
|
match self {
|
|
Material::Lambertian { albedo } => {
|
|
let scatter_dir = rec.normal + Vec3::rand_unit_vector(srng);
|
|
// The compiler might be smart enough to compute this ^^^ just once. In which case,
|
|
// I don't need to do this weird dance. Oh well. It'll work.
|
|
let scatter_dir = if scatter_dir.near_zero() { // if near zero,
|
|
rec.normal // replace with normal
|
|
} else {
|
|
scatter_dir // else preserve current
|
|
};
|
|
|
|
//TODO: Revisit this out-parameter pattern
|
|
// It's a side effect of C++'s obtuse move semantics (and the RTIOW author not
|
|
// using them at all)
|
|
*scattered = Ray{
|
|
orig: rec.p,
|
|
dir: scatter_dir
|
|
};
|
|
*attenuation = *albedo; // deref on both sides? Wacky
|
|
return true;
|
|
},
|
|
Material::Metal { albedo, fuzz } => {
|
|
let reflected = Vec3::reflect(
|
|
Vec3::as_unit(ray_in.dir),
|
|
rec.normal
|
|
);
|
|
*scattered = Ray{
|
|
orig: rec.p,
|
|
dir: reflected + Vec3::rand_in_unit_sphere(srng) * *fuzz,
|
|
};
|
|
*attenuation = *albedo;
|
|
return Vec3::dot(scattered.dir, rec.normal) > 0.0;
|
|
},
|
|
Material::Dielectric { index_refraction } => {
|
|
*attenuation = Vec3::ones();
|
|
let refraction_ratio = if rec.front_face { 1.0 / index_refraction } else { *index_refraction };
|
|
|
|
let unit_direction = Vec3::as_unit(ray_in.dir);
|
|
let cos_theta = Vec3::dot(-unit_direction, rec.normal).min(1.0);
|
|
let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
|
|
|
|
let cannot_refract = refraction_ratio * sin_theta > 1.0;
|
|
let distrib_zero_one = Uniform::new(0.0, 1.0);
|
|
let direction = if cannot_refract || Material::reflectance(cos_theta, refraction_ratio) > srng.sample(distrib_zero_one) {
|
|
Vec3::reflect(unit_direction, rec.normal)
|
|
} else {
|
|
Vec3::refract(unit_direction, rec.normal, refraction_ratio)
|
|
};
|
|
*scattered = Ray {
|
|
orig: rec.p,
|
|
dir: direction
|
|
};
|
|
return true;
|
|
},
|
|
}
|
|
}
|
|
|
|
fn reflectance(cosine: f32, ref_idx: f32) -> f32 {
|
|
// Schlick's approximation for reflectance.
|
|
let r0 = (1.0 - ref_idx) / (1.0 + ref_idx);
|
|
let r0 = r0 * r0;
|
|
return r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0);
|
|
}
|
|
}
|
|
|
|
// Camera
|
|
|
|
pub fn degrees_to_radians(degrees: f32) -> f32 {
|
|
degrees * std::f32::consts::PI / 180.0
|
|
}
|
|
|
|
#[derive (Clone, Copy)]
|
|
pub struct Camera {
|
|
origin: Vec3,
|
|
lower_left_corner: Vec3,
|
|
horizontal: Vec3,
|
|
vertical: Vec3,
|
|
u: Vec3, v: Vec3, /*w: Vec3,*/
|
|
lens_radius: f32,
|
|
}
|
|
|
|
impl Camera {
|
|
pub fn new(
|
|
lookfrom: Vec3,
|
|
lookat: Vec3,
|
|
vup: Vec3,
|
|
vfov: f32,
|
|
aspect_ratio: f32,
|
|
aperture: f32,
|
|
focus_dist: f32
|
|
) -> Camera {
|
|
let theta = degrees_to_radians(vfov);
|
|
let h = (theta / 2.0).tan();
|
|
let vp_height = 2.0 * h;
|
|
let vp_width = aspect_ratio * vp_height;
|
|
|
|
let w = Vec3::as_unit(lookfrom - lookat);
|
|
let u = Vec3::as_unit(Vec3::cross(vup, w));
|
|
let v = Vec3::cross(w, u);
|
|
|
|
let orig = lookfrom;
|
|
let horiz = u * vp_width * focus_dist;
|
|
let verti = v * vp_height * focus_dist;
|
|
let lower_left_corner = orig - horiz / 2.0 - verti / 2.0 - w * focus_dist;
|
|
|
|
Camera{
|
|
origin: orig,
|
|
lower_left_corner,
|
|
horizontal: horiz,
|
|
vertical: verti,
|
|
u, v, /* w,*/
|
|
lens_radius: aperture / 2.0,
|
|
}
|
|
}
|
|
|
|
pub fn get_ray(&self, s: f32, t: f32, srng: &mut SmallRng) -> Ray {
|
|
let rd = Vec3::rand_in_unit_disk(srng) * self.lens_radius;
|
|
let offset = self.u * rd.x + self.v * rd.y;
|
|
|
|
let dir = self.lower_left_corner
|
|
+ self.horizontal * s
|
|
+ self.vertical * t
|
|
- self.origin - offset;
|
|
Ray{
|
|
orig: self.origin + offset,
|
|
dir,
|
|
}
|
|
}
|
|
}
|
|
|