rustracer/src/hittable.rs

203 lines
6.2 KiB
Rust

use crate::{Point3, Ray, Vec3};
use crate::material::{Material};
use crate::aabb::Aabb;
use std::f64::consts::PI;
use std::sync::Arc;
pub type HittableObject = Arc<dyn Hittable + Sync + Send>;
pub type HittableList = Vec<HittableObject>;
//#[derive(Debug, Clone, Copy)]
#[derive(Clone, Copy)]
pub struct HitRecord<'material> {
pub point: Point3,
pub normal: Vec3,
pub t: f64,
pub u: f64,
pub v: f64,
pub front_face: bool,
pub material: &'material Arc<Material>
}
impl<'material> HitRecord<'material> {
pub fn normalized(&mut self, ray: &Ray) {
let dot = ray.direction().dot(&self.normal);
let front_face = dot < 0.0;
let normal = if front_face { self.normal } else { -self.normal };
self.normal = normal;
self.front_face = front_face;
}
/*pub fn normalized(& self) -> Self {
let dot = ray.direction().dot(self.normal);
let front_face = dot < 0.0;
let normal = if front_face { self.normal } else { -self.normal };
HitRecord {
point: self.point,
normal,
t: self.t,
u: self.u,
v: self.v,
front_face,
material: self.material
}
}*/
}
pub trait Hittable {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb>;
}
//#[derive(Clone)]
pub struct Sphere {
pub center: Point3,
pub radius: f64,
pub material: Arc<Material>
}
impl Sphere {
pub fn new(center: Point3, radius: f64, material: Arc<Material>) -> Sphere {
Sphere { center, radius, material }
}
pub fn uv(point: &Point3) -> (f64, f64) {
// p: a given point on the sphere of radius one, centered at the origin.
// u: returned value [0,1] of angle around the Y axis from X=-1.
// v: returned value [0,1] of angle from Y=-1 to Y=+1.
// <1 0 0> yields <0.50 0.50> <-1 0 0> yields <0.00 0.50>
// <0 1 0> yields <0.50 1.00> < 0 -1 0> yields <0.50 0.00>
// <0 0 1> yields <0.25 0.50> < 0 0 -1> yields <0.75 0.50>
let theta = -(point.y()).acos();
let phi = -(point.z()).atan2(point.x()) + PI;
(phi / (2.0 * PI), theta / PI)
}
}
impl Default for Sphere {
fn default() -> Self {
Sphere {
center: Point3::default(),
radius: 0.0,
material: Arc::new(Material::default())
}
}
}
impl Hittable for Sphere {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let oc = &ray.origin() - &self.center;
let a = ray.direction().length_squared();
let half_b = oc.dot(&ray.direction());
let c = oc.length_squared() - &self.radius * &self.radius;
let discriminant = half_b * half_b - a * c;
if discriminant < 0.0 { return None; }
let sqrtd = discriminant.sqrt();
let mut root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
root = (-half_b + sqrtd) / a;
if root < t_min || t_max < root { return None; }
}
let point = ray.at(root);
let normal = (point - self.center) / self.radius;
let (u, v) = Sphere::uv(&normal);
let mut rec = HitRecord {
point,
normal,
t: root,
u,
v,
front_face: false, // Will be set during normalied()
material: &self.material
};
rec.normalized(ray);
Some(rec)
}
fn bounding_box(&self, _time0: f64, _time1: f64) -> Option<Aabb> {
let s = Vec3::new(self.radius, self.radius, self.radius);
Some(Aabb {
minimum: self.center - s,
maximum: self.center + s,
})
}
}
//#[derive(Clone)]
pub struct MovableSphere {
pub center0: Point3,
pub center1: Point3,
pub radius: f64,
pub material: Arc<Material>,
pub time0: f64,
pub time1: f64,
}
impl MovableSphere {
pub fn new(
center0: Point3,
center1: Point3,
radius: f64,
material: Arc<Material>,
time0: f64,
time1: f64) -> Self {
MovableSphere { center0, center1, radius, material, time0, time1 }
}
pub fn center(&self, time: f64) -> Point3 {
self.center0 + ((time - self.time0) / (self.time1 - self.time0))*(self.center1 - self.center0)
}
}
impl Default for MovableSphere {
fn default() -> Self {
MovableSphere {
center0: Point3::default(),
center1: Point3::default(),
radius: 0.0,
material: Arc::new(Material::default()),
time0: 0.0,
time1: 0.0
}
}
}
impl Hittable for MovableSphere {
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
let oc = &ray.origin() - &self.center(ray.time());
let a = ray.direction().length_squared();
let half_b = oc.dot(&ray.direction());
let c = oc.length_squared() - &self.radius * &self.radius;
let discriminant = half_b * half_b - a * c;
if discriminant < 0.0 { return None; }
let sqrtd = discriminant.sqrt();
let mut root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
root = (-half_b + sqrtd) / a;
if root < t_min || t_max < root { return None; }
}
let point = ray.at(root);
let normal = (point - self.center(ray.time())) / self.radius;
let dot = ray.direction().dot(&normal);
let front_face = dot < 0.0;
let (u, v) = Sphere::uv(&normal);
let normal = if front_face { normal } else { -normal };
Some(HitRecord {
point,
normal,
t: root,
u,
v,
front_face,
material: &self.material,
})
}
fn bounding_box(&self, time0: f64, time1: f64) -> Option<Aabb> {
let s = Vec3::new(self.radius, self.radius, self.radius);
let center0 = self.center(time0);
let center1 = self.center(time1);
Some(Aabb {
minimum: center0 - s,
maximum: center0 + s,
}.surrounding(&Aabb {
minimum: center1 - s,
maximum: center1 + s
}))
}
}