use crate::vectors::Vec3;
pub trait RayCollision {
fn ray_intersect(&self, ray: &Ray) -> HitPoint;
fn collision_normal(&self, hit_point: Vec3) -> Vec3;
fn collision_material(&self, hit_point: Vec3) -> Material;
}
#[derive(Debug)]
pub struct Plane {
pub normal: Vec3,
pub point: Vec3,
}
impl RayCollision for Plane {
fn ray_intersect(&self, ray: &Ray) -> HitPoint {
let orig_to_point = self.point - ray.origin;
let origin_to_plane_dist = self.normal.dot(&orig_to_point);
let cos_dir_norm = self.normal.dot(&ray.direction);
if cos_dir_norm * origin_to_plane_dist < 0. {
HitPoint::None
} else {
let dist_to_collision = origin_to_plane_dist / cos_dir_norm;
HitPoint::Point(ray.walk_dir(dist_to_collision))
}
}
fn collision_normal(&self, hit_point: Vec3) -> Vec3 {
self.normal
}
fn collision_material(&self, hit_point: Vec3) -> Material {
Material::default()
}
}
#[derive(Debug)]
pub struct Rectangle2D {
width: Vec3,
height: Vec3,
plane: Plane,
material: Material,
}
impl Rectangle2D {
pub fn new(origin: Vec3, center: Vec3, side_dir: Vec3, material: Material) -> Self {
let z = center - origin;
let e1 = side_dir.normalized();
let u = z.project_on(&e1);
let e2 = (z - u).normalized();
let w = 2. * z.project_on(&e1).l2();
let h = 2. * z.project_on(&e2).l2();
let normal = e1.cross(&e2);
let plane = Plane {
normal,
point: origin,
};
Self {
width: e1.mult(w),
height: e2.mult(h),
plane,
material,
}
}
}
impl RayCollision for Rectangle2D {
fn ray_intersect(&self, ray: &Ray) -> HitPoint {
match self.plane.ray_intersect(ray) {
HitPoint::None => HitPoint::None,
HitPoint::Point(p) => {
let d = p - self.plane.point;
let w_porj = d.project_on(&self.width);
let h_proj = d.project_on(&self.height);
if let (true, true, true, true) = (
w_porj.l2() <= self.width.l2(),
h_proj.l2() <= self.height.l2(),
w_porj.dot(&self.width) > 0.,
h_proj.dot(&self.height) > 0.
){
HitPoint::Point(p)
} else {
HitPoint::None
}
}
}
}
fn collision_normal(&self, hit_point: Vec3) -> Vec3 {
self.plane.normal
}
fn collision_material(&self, hit_point: Vec3) -> Material {
self.material
}
}
#[derive(Debug, Clone, Copy)]
pub struct Sphere {
pub center: Vec3,
pub radius: f32,
pub material: Material,
}
pub enum HitPoint {
Point(Vec3),
None,
}
impl RayCollision for Sphere {
fn ray_intersect(&self, ray: &Ray) -> HitPoint {
let canonical_center = self.center - ray.origin;
let center_projected_ray = canonical_center.project_on(&ray.direction);
let dist_ctr_proj = (canonical_center - center_projected_ray).l2();
if dist_ctr_proj > self.radius {
return HitPoint::None;
}
let dist_proj_intersect1 = (self.radius.powf(2.0) - dist_ctr_proj.powf(2.)).sqrt();
let dist_orig_proj = canonical_center.dot(&ray.direction);
match (
dist_orig_proj - dist_proj_intersect1,
dist_orig_proj + dist_proj_intersect1,
) {
(o_i1, _) if o_i1 > 0. => HitPoint::Point(ray.walk_dir(o_i1)),
(_, o_i2) if o_i2 > 0. => HitPoint::Point(ray.walk_dir(o_i2)),
_ => HitPoint::None,
}
}
fn collision_normal(&self, hit_point: Vec3) -> Vec3 {
(hit_point - self.center).normalized()
}
fn collision_material(&self, hit_point: Vec3) -> Material {
self.material
}
}
#[derive(Clone, Copy)]
pub struct LightSource {
pub position: Vec3,
pub intensity: f32,
}
#[derive(Clone, Copy)]
pub struct Ray {
pub origin: Vec3,
pub direction: Vec3,
}
impl Ray {
pub fn new(dir: Vec3) -> Self {
Self {
origin: Vec3::orig(),
direction: dir.normalized(),
}
}
pub fn set_origin(mut self, origin: Vec3) -> Self {
self.origin = origin;
self
}
pub fn walk_dir(&self, distance: f32) -> Vec3 {
self.origin + self.direction.mult(distance)
}
}
#[derive(Clone, Copy, Debug)]
pub struct Material {
color: (f32, f32, f32),
pub pixel: image::Rgb<u8>,
pub specular_exponent: f32,
pub refraction_index: f32,
diff_mixing_coef: f32,
spec_mixing_coef: f32,
reflection_mixing_coef: f32,
refraction_mixing_coef: f32,
}
type MaterialMixingWeights = (f32, f32, f32, f32);
impl Material {
fn _to_pixel(color: (f32, f32, f32)) -> image::Rgb<u8> {
let (r, g, b) = color;
image::Rgb([(255. * r) as u8, (255. * g) as u8, (255. * b) as u8])
}
pub fn new(
color: (f32, f32, f32),
weights: MaterialMixingWeights,
specular_exponent: f32,
refraction_index: f32,
) -> Self {
let pixel = Self::_to_pixel(color);
let (diff_mixing_coef, spec_mixing_coef, reflection_mixing_coef, refraction_mixing_coef) =
weights;
Self {
color,
pixel,
specular_exponent,
refraction_index,
diff_mixing_coef,
spec_mixing_coef,
reflection_mixing_coef,
refraction_mixing_coef,
}
}
pub fn adjust_light(mut self, diffuse: f32, specular: f32) -> Self {
let (r, g, b) = self.color;
let diff_albedo = diffuse * self.diff_mixing_coef;
let white_shift = specular * self.spec_mixing_coef;
self.color = (
(r * diff_albedo + white_shift).max(0.).min(1.),
(g * diff_albedo + white_shift).max(0.).min(1.),
(b * diff_albedo + white_shift).max(0.).min(1.),
);
self.pixel = Self::_to_pixel(self.color);
self
}
fn _mix_materials(mut self, other: Material, coef: f32) -> Self {
let (r1, g1, b1) = self.color;
let (r2, g2, b2) = other.color;
let mixed_color = (
(r1 + coef * r2).max(0.).min(1.),
(g1 + coef * g2).max(0.).min(1.),
(b1 + coef * b2).max(0.).min(1.),
);
self.color = mixed_color;
self.pixel = Self::_to_pixel(self.color);
self
}
pub fn mix_reflection(self, other: Material) -> Self {
self._mix_materials(other, self.reflection_mixing_coef)
}
pub fn mix_refraction(self, other: Material) -> Self {
self._mix_materials(other, self.refraction_mixing_coef)
}
}
impl Default for Material {
fn default() -> Self {
let weights: MaterialMixingWeights = (1.0, 0.0, 0.0, 0.);
Self::new((0.2, 0.7, 0.8), weights, 1.0, 1.0)
}
}