use crate::math::{Hit, Ray};
use glam::Vec3;
#[derive(Clone, Copy)]
pub struct Material {
pub albedo: Vec3,
pub specular: f32,
pub shininess: f32,
pub reflectivity: f32,
pub pattern: u8,
}
impl Material {
pub const fn lambert(albedo: Vec3) -> Self {
Self { albedo, specular: 0.1, shininess: 8.0, reflectivity: 0.0, pattern: 0 }
}
pub const fn shiny(albedo: Vec3, reflectivity: f32) -> Self {
Self { albedo, specular: 0.6, shininess: 64.0, reflectivity, pattern: 0 }
}
pub const fn checker(a: Vec3) -> Self {
Self { albedo: a, specular: 0.05, shininess: 4.0, reflectivity: 0.0, pattern: 1 }
}
pub fn sample_albedo(&self, p: Vec3) -> Vec3 {
match self.pattern {
1 => {
let s = 1.0;
let ix = (p.x * s).floor() as i32;
let iz = (p.z * s).floor() as i32;
if (ix + iz).rem_euclid(2) == 0 {
self.albedo
} else {
self.albedo * 0.25
}
}
_ => self.albedo,
}
}
}
pub struct Sphere {
pub center: Vec3,
pub radius: f32,
pub material: usize,
}
impl Sphere {
pub fn hit(&self, r: &Ray, t_min: f32, t_max: f32) -> Option<Hit> {
let oc = r.origin - self.center;
let b = oc.dot(r.dir);
let c = oc.dot(oc) - self.radius * self.radius;
let disc = b * b - c;
if disc < 0.0 {
return None;
}
let sq = disc.sqrt();
let mut t = -b - sq;
if t < t_min || t > t_max {
t = -b + sq;
if t < t_min || t > t_max {
return None;
}
}
let point = r.at(t);
let normal = (point - self.center) / self.radius;
Some(Hit { t, point, normal, material: self.material })
}
}
pub struct Plane {
pub point: Vec3,
pub normal: Vec3,
pub material: usize,
}
impl Plane {
pub fn hit(&self, r: &Ray, t_min: f32, t_max: f32) -> Option<Hit> {
let denom = self.normal.dot(r.dir);
if denom.abs() < 1e-5 {
return None;
}
let t = (self.point - r.origin).dot(self.normal) / denom;
if t < t_min || t > t_max {
return None;
}
let n = if denom < 0.0 { self.normal } else { -self.normal };
Some(Hit { t, point: r.at(t), normal: n, material: self.material })
}
}
pub struct Light {
pub position: Vec3,
pub color: Vec3,
pub intensity: f32,
}
pub struct Scene {
pub spheres: Vec<Sphere>,
pub planes: Vec<Plane>,
pub materials: Vec<Material>,
pub lights: Vec<Light>,
pub ambient: Vec3,
pub sky_top: Vec3,
pub sky_bottom: Vec3,
}
impl Scene {
pub fn hit(&self, r: &Ray, t_min: f32, t_max: f32) -> Option<Hit> {
let mut closest = t_max;
let mut best: Option<Hit> = None;
for s in &self.spheres {
if let Some(h) = s.hit(r, t_min, closest) {
closest = h.t;
best = Some(h);
}
}
for p in &self.planes {
if let Some(h) = p.hit(r, t_min, closest) {
closest = h.t;
best = Some(h);
}
}
best
}
pub fn sky(&self, dir: Vec3) -> Vec3 {
let t = (dir.y * 0.5 + 0.5).clamp(0.0, 1.0);
self.sky_bottom.lerp(self.sky_top, t)
}
pub fn demo() -> Self {
let materials = vec![
Material::checker(Vec3::new(0.85, 0.85, 0.85)), Material::shiny(Vec3::new(0.95, 0.25, 0.35), 0.35), Material::shiny(Vec3::new(0.20, 0.55, 0.95), 0.45), Material::lambert(Vec3::new(0.95, 0.85, 0.20)), Material::shiny(Vec3::new(0.85, 0.85, 0.90), 0.85), ];
let spheres = vec![
Sphere { center: Vec3::new(0.0, 1.0, 0.0), radius: 1.0, material: 1 },
Sphere { center: Vec3::new(-2.4, 0.8, -0.5), radius: 0.8, material: 2 },
Sphere { center: Vec3::new(2.0, 0.6, 0.8), radius: 0.6, material: 3 },
Sphere { center: Vec3::new(0.6, 0.4, 2.2), radius: 0.4, material: 4 },
];
let planes = vec![
Plane { point: Vec3::ZERO, normal: Vec3::Y, material: 0 },
];
let lights = vec![
Light { position: Vec3::new(4.0, 6.0, 3.0), color: Vec3::new(1.0, 0.95, 0.85), intensity: 1.2 },
Light { position: Vec3::new(-5.0, 4.0, -2.0), color: Vec3::new(0.5, 0.6, 1.0), intensity: 0.6 },
];
Self {
spheres,
planes,
materials,
lights,
ambient: Vec3::new(0.08, 0.09, 0.12),
sky_top: Vec3::new(0.45, 0.65, 0.95),
sky_bottom: Vec3::new(0.85, 0.80, 0.70),
}
}
}