178 lines
5.4 KiB
Rust
178 lines
5.4 KiB
Rust
use crate::{Point3, Ray, Vec3};
|
|
use crate::material::{Material};
|
|
use crate::aabb::Aabb;
|
|
use std::f64::consts::PI;
|
|
|
|
pub type HittableList = Vec<Box<dyn Hittable + Sync>>;
|
|
|
|
//#[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 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: Material
|
|
}
|
|
impl Sphere {
|
|
pub fn new(center: Point3, radius: f64, material: 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: 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 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);
|
|
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: Material,
|
|
pub time0: f64,
|
|
pub time1: f64,
|
|
}
|
|
impl MovableSphere {
|
|
pub fn new(
|
|
center0: Point3,
|
|
center1: Point3,
|
|
radius: f64,
|
|
material: 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: 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
|
|
}))
|
|
}
|
|
}
|