Compare commits

...

4 Commits

Author SHA1 Message Date
076bcc7155 ft: deserialization 2026-04-26 23:49:03 +02:00
430bdf63bc ft (wip): deserialization 2026-04-25 14:46:03 +02:00
ef8da70436 ft (wip): random scene generation 2026-04-25 05:20:15 +02:00
5f2c419af5 ft: 12: movable camera + camera init overhaul 2026-04-25 03:55:44 +02:00
16 changed files with 623 additions and 157 deletions

3
Cargo.lock generated
View File

@@ -1240,6 +1240,8 @@ dependencies = [
"ops", "ops",
"pretty_env_logger", "pretty_env_logger",
"rand 0.10.1", "rand 0.10.1",
"serde",
"serde_json",
] ]
[[package]] [[package]]
@@ -1311,6 +1313,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e"
dependencies = [ dependencies = [
"serde_core", "serde_core",
"serde_derive",
] ]
[[package]] [[package]]

View File

@@ -11,3 +11,5 @@ log = "0.4.29"
ops = "0.6.0" ops = "0.6.0"
pretty_env_logger = "0.5.0" pretty_env_logger = "0.5.0"
rand = "0.10.1" rand = "0.10.1"
serde = {version = "1.0.228", features = ["derive"]}
serde_json = "1.0.149"

View File

@@ -0,0 +1,22 @@
{
"camera": {
"image_width": 1920,
"image_height": 1080,
"anti_alias_rate": 1,
"max_depth": 10,
"fov": 90.0,
"look_from": { "x": -10, "y": 5, "z": 10 },
"look_at": { "x": 0.0, "y": 0.0, "z": -1.0 },
"vup": { "x": 0.0, "y": 1.0, "z": 0.0 }
},
"materials": [
{ "type": "metal", "albedo": { "x": 0.2, "y": 0.4, "z": 0.8 }, "prob": 1.0, "fuzz": 0.1 }
],
"objects": [
{ "type": "sphere", "center": { "x": 0, "y": 0.7, "z": -0.4 }, "radius": 0.2, "material": 1}
]
}

34
scenes/scene.json Normal file
View File

@@ -0,0 +1,34 @@
{
"camera": {
"image_width": 1920,
"image_height": 1080,
"anti_alias_rate": 23,
"max_depth": 100,
"fov": 40.0,
"look_from": { "x": -10, "y": 5, "z": 10 },
"look_at": { "x": 0.0, "y": 0.0, "z": -1.0 },
"vup": { "x": 0.0, "y": 1.0, "z": 0.0 }
},
"materials": [
{ "type": "metal", "albedo": { "x": 0.2, "y": 0.4, "z": 0.8 }, "prob": 1.0, "fuzz": 0.1 },
{ "type": "metal", "albedo": { "x": 0.7, "y": 0.4, "z": 0.2 }, "prob": 1.0, "fuzz": 0.1 },
{ "type": "lambertian", "albedo": { "x": 0.8, "y": 0.8, "z": 0.0 }, "prob": 1.0 },
{ "type": "lambertian", "albedo": { "x": 0.1, "y": 0.2, "z": 0.5 }, "prob": 1.0 },
{ "type": "dielectric", "refraction_index": 1.5},
{ "type": "dielectric", "refraction_index": 0.67},
{ "type": "metal", "albedo": { "x": 0.8, "y": 0.6, "z": 0.2 }, "prob": 1.0, "fuzz": 1.0 }
],
"objects": [
{ "type": "sphere", "center": { "x": 0, "y": 0.7, "z": -0.4 }, "radius": 0.2, "material": 0},
{ "type": "sphere", "center": { "x": 0.0, "y": 0.5, "z": -0.8 }, "radius": 0.1, "material": 1},
{ "type": "sphere", "center": { "x": 0.0, "y": -100.5, "z": -1.0 }, "radius": 100.0, "material": 2},
{ "type": "sphere", "center": { "x": 0.0, "y": 0.0, "z": -1.2 }, "radius": 0.5, "material": 3},
{ "type": "sphere", "center": { "x": -1, "y": 0, "z": -1 }, "radius": 0.4, "material": 5},
{ "type": "sphere", "center": { "x": 1, "y": 0, "z": -1 }, "radius": 0.5, "material": 6},
{ "type": "sphere", "center": { "x": 20, "y": 7, "z": -15 }, "radius": 10.5, "material": 0}
]
}

View File

@@ -1,6 +1,7 @@
use std::cmp::max; use std::{f32::consts::PI, sync::Arc};
use log::info; use log::info;
use serde::Deserialize;
use crate::{ use crate::{
objects::{hit::Hit, traits::Hittable}, objects::{hit::Hit, traits::Hittable},
@@ -8,50 +9,157 @@ use crate::{
vec3::{Colour, Vec3}, vec3::{Colour, Vec3},
}; };
#[derive(Debug)]
pub struct Camera { pub struct Camera {
// output
image_width: u32, image_width: u32,
image_height: u32, image_height: u32,
// raytracing
anti_alias_rate: u32, anti_alias_rate: u32,
max_depth: u32, max_depth: u32,
center: Vec3,
pixel00_loc: Vec3, pixel00_loc: Vec3,
pixel_delta_u: Vec3, pixel_delta_u: Vec3,
pixel_delta_v: Vec3, pixel_delta_v: Vec3,
// camera
dirty: bool,
fov: f32,
look_from: Vec3,
look_at: Vec3,
vup: Vec3,
// camera helpers
u: Vec3,
v: Vec3,
w: Vec3,
}
// FIXME: kinda out of place in this file.
fn deg_to_rad(deg: f32) -> f32 {
deg * PI / 180.
} }
impl Camera { impl Camera {
pub fn new(aspect_ratio: f32, image_width: u32, anti_alias_rate: u32, max_depth: u32) -> Self { pub fn new(image_width: u32, image_height: u32) -> Self {
let image_height = max(1, (image_width as f32 / aspect_ratio) as u32);
//camera
let focal_length = 1.;
let viewport_height = 2.;
let viewport_width = viewport_height * (image_width as f32 / image_height as f32);
let camera_center = Vec3::nil();
//viewport
let viewport_u = Vec3::new(viewport_width as f32, 0., 0.);
let viewport_v = Vec3::new(0., -viewport_height as f32, 0.);
let pixel_delta_u = viewport_u / image_width as f32;
let pixel_delta_v = viewport_v / image_height as f32;
let pixel00_loc =
camera_center - Vec3::new(0., 0., focal_length) - viewport_u / 2 - viewport_v / 2;
Self { Self {
image_width: image_width, image_width,
image_height: image_height, image_height,
anti_alias_rate: anti_alias_rate, anti_alias_rate: 1,
max_depth: max_depth, max_depth: 10,
center: camera_center, dirty: true,
pixel00_loc: pixel00_loc, fov: 60.,
pixel_delta_u: pixel_delta_u, pixel00_loc: Vec3::default(),
pixel_delta_v: pixel_delta_v, pixel_delta_u: Vec3::default(),
pixel_delta_v: Vec3::default(),
look_from: Vec3::nil(),
look_at: Vec3::new(0., 0., -1.),
vup: Vec3::new(0., 1., 0.),
u: Vec3::default(),
v: Vec3::default(),
w: Vec3::default(),
} }
} }
pub fn render<T: Hittable>(&self, hittables: &Vec<T>) { pub fn new_full(
image_width: u32,
image_height: u32,
anti_alias_rate: u32,
max_depth: u32,
fov: f32,
look_from: Vec3,
look_at: Vec3,
vup: Vec3,
) -> Self {
Self {
image_width,
image_height,
anti_alias_rate,
max_depth,
pixel00_loc: Vec3::default(),
pixel_delta_u: Vec3::default(),
pixel_delta_v: Vec3::default(),
dirty: true,
fov,
look_from,
look_at,
vup,
u: Vec3::default(),
v: Vec3::default(),
w: Vec3::default(),
}
}
fn init(&mut self) {
// camera
let focal_length = (self.look_from - self.look_at).length();
let theta = deg_to_rad(self.fov);
let h = (theta / 2.).tan();
self.w = (self.look_from - self.look_at).get_unit();
self.u = self.vup.cross(&self.w).get_unit();
self.v = self.w.cross(&self.u);
// viewport
let viewport_height = 2. * h * focal_length;
let viewport_width = viewport_height * (self.image_width as f32 / self.image_height as f32);
let viewport_u = viewport_width * self.u;
let viewport_v = viewport_height * -self.v;
// variables
self.pixel_delta_u = viewport_u / self.image_width as f32;
self.pixel_delta_v = viewport_v / self.image_height as f32;
self.pixel00_loc =
self.look_from - (focal_length * self.w) - viewport_u / 2. - viewport_v / 2.;
self.dirty = false;
}
pub fn set_fov(&mut self, fov: f32) {
if self.fov != fov {
self.fov = fov;
self.dirty = true;
}
}
pub fn set_max_depth(&mut self, depth: u32) {
if self.max_depth != depth {
self.max_depth = depth;
self.dirty = true;
}
}
pub fn set_anti_alias_rate(&mut self, rate: u32) {
if self.anti_alias_rate != rate {
self.anti_alias_rate = rate;
self.dirty = true;
}
}
pub fn set_look_from(&mut self, look_from: Vec3) {
if self.look_from != look_from {
self.look_from = look_from;
self.dirty = true;
}
}
pub fn set_look_at(&mut self, look_at: Vec3) {
if self.look_at != look_at {
self.look_at = look_at;
self.dirty = true;
}
}
pub fn set_vup(&mut self, vup: Vec3) {
if self.vup != vup {
self.vup = vup;
self.dirty = true;
}
}
pub fn render(&mut self, hittables: &Vec<Arc<dyn Hittable>>) {
if self.dirty {
self.init()
}
let mut imgbuf = image::ImageBuffer::new(self.image_width, self.image_height); let mut imgbuf = image::ImageBuffer::new(self.image_width, self.image_height);
// render // render
@@ -61,14 +169,14 @@ impl Camera {
let pixel_tl = let pixel_tl =
self.pixel00_loc + (i * self.pixel_delta_u) + (j * self.pixel_delta_v); self.pixel00_loc + (i * self.pixel_delta_u) + (j * self.pixel_delta_v);
let mut pixel_colour = Colour::nil(); let mut pixel_colour = Colour::default();
for y in 1..(self.anti_alias_rate + 1) { for y in 1..(self.anti_alias_rate + 1) {
for x in 1..(self.anti_alias_rate + 1) { for x in 1..(self.anti_alias_rate + 1) {
let pixel_loc = pixel_tl let pixel_loc = pixel_tl
+ (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32) + (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32)
+ (y * self.pixel_delta_v / (self.anti_alias_rate + 1) as f32); + (y * self.pixel_delta_v / (self.anti_alias_rate + 1) as f32);
let ray_dir = pixel_loc - self.center; let ray_dir = pixel_loc - self.look_from;
let r = Ray::new(self.center, ray_dir); let r = Ray::new(self.look_from, ray_dir);
pixel_colour += self.ray_colour(&hittables, &r, self.max_depth); pixel_colour += self.ray_colour(&hittables, &r, self.max_depth);
} }
} }
@@ -83,9 +191,9 @@ impl Camera {
imgbuf.save("output.png").unwrap(); imgbuf.save("output.png").unwrap();
} }
fn ray_colour<T: Hittable>(&self, hittables: &Vec<T>, r: &Ray, depth: u32) -> Colour { fn ray_colour(&self, hittables: &Vec<Arc<dyn Hittable>>, r: &Ray, depth: u32) -> Colour {
if depth <= 0 { if depth <= 0 {
return Colour::nil(); return Colour::default();
} }
let closest = Hit::hit_list(hittables, r); let closest = Hit::hit_list(hittables, r);
@@ -93,7 +201,7 @@ impl Camera {
if let Some((scattered, att)) = hit.mat().scatter(&hit, r) { if let Some((scattered, att)) = hit.mat().scatter(&hit, r) {
return att * self.ray_colour(hittables, &scattered, depth - 1); return att * self.ray_colour(hittables, &scattered, depth - 1);
} }
return Colour::nil(); return Colour::default();
} }
// background // background

View File

@@ -3,40 +3,110 @@
mod camera; mod camera;
mod objects; mod objects;
mod ray; mod ray;
mod scenes;
mod vec3; mod vec3;
use std::fs;
use std::sync::Arc; use std::sync::Arc;
use crate::camera::Camera;
use crate::objects::materials::dielectric::Dielectric; use crate::objects::materials::dielectric::Dielectric;
use crate::objects::materials::lambertian::{Lambertian, Metal}; use crate::objects::materials::lambertian::{Lambertian, Metal};
use crate::objects::materials::traits::Material;
use crate::objects::sphere::Sphere; use crate::objects::sphere::Sphere;
use crate::objects::traits::Hittable;
use crate::ray::Ray; use crate::ray::Ray;
use crate::scenes::scene::Scene;
use crate::vec3::Vec3; use crate::vec3::Vec3;
use dotenv::dotenv; use dotenv::dotenv;
use log::info;
use pretty_env_logger; use pretty_env_logger;
use rand::seq::IndexedRandom;
use rand::RngExt;
fn random_material() -> Arc<dyn Material> {
let mut rng = rand::rng();
let col = Vec3::new(rng.random(), rng.random(), rng.random());
match rng.random_range(0..3) {
0 => Arc::new(Lambertian::new(col, 1.)),
1 => Arc::new(Metal::new(col, 1., rng.random())),
2 => Arc::new(Dielectric::new(rng.random_range(0.3..1.9))),
_ => Arc::new(Metal::new(col, 0., rng.random())),
}
}
fn random_sphere_on_floor<T: Hittable>(
materials: &Vec<Arc<dyn Material>>,
existing: &Vec<T>,
max_size: f32,
) -> Sphere {
let mut rng = rand::rng();
let r = rng.random_range(0.1..max_size);
let mut sphere = Sphere::xyz(
rng.random_range((-50.)..50.),
rng.random_range(0.1..max_size),
rng.random_range((-50.)..50.),
r,
materials.choose(&mut rng).unwrap().clone(),
);
let mut isct = true;
let mut attempts_left = 49;
while isct && attempts_left > 0 {
isct = false;
for h in existing {
if h.inside(&h.closest_on_surface(sphere.center())) {
info!("Generating sphere failed, {} attempts left", attempts_left);
isct = true;
continue;
}
}
if isct {
sphere.set_center(Vec3::new(
rng.random_range(0.5..100.),
r,
rng.random_range(0.5..100.),
));
}
attempts_left -= 1;
}
sphere
}
// TODO: implement scene serialization
fn main() { fn main() {
dotenv().ok(); dotenv().ok();
pretty_env_logger::init(); pretty_env_logger::init();
// setup objects // TODO: use cli arg for scenefile
let blue = Arc::new(Metal::rgb(0.2, 0.4, 0.8, 1., 0.1)); let json_file = "./scenes/scenes.json";
let metal = Arc::new(Metal::rgb(0.7, 0.4, 0.2, 1., 0.1)); // let json_file = "./scenes/failsMatBounds.json";
let ground = Arc::new(Lambertian::rgb(0.8, 0.8, 0., 1.0)); let json_str = fs::read_to_string(json_file).expect("Reading specified scene file failed!");
let center = Arc::new(Lambertian::rgb(0.1, 0.2, 0.5, 1.)); let mut scene: Scene = serde_json::from_str(&json_str).unwrap();
let left = Arc::new(Dielectric::new(1.5)); scene.render();
let bubble = Arc::new(Dielectric::new(1. / 1.5));
let right = Arc::new(Metal::rgb(0.8, 0.6, 0.2, 1., 1.0));
let mut world = vec![Sphere::xyz(0., 0.5, -0.8, 0.1, metal.clone())]; // random spheres code; thought: make this available as cli flag?
world.push(Sphere::xyz(0., -100.5, -1., 100., ground.clone())); // let mut materials: Vec<Arc<dyn Material>> = vec![Arc::new(Lambertian::rgb(0.1, 0.1, 0.2, 0.8))];
world.push(Sphere::xyz(0., 0., -1.2, 0.5, center.clone())); // for i in 0..15 {
world.push(Sphere::xyz(-1., 0., -1.0, 0.5, left.clone())); // info!("Generating {}th material.", i + 1);
world.push(Sphere::xyz(-1., 0., -1.0, 0.4, bubble.clone())); // materials.push(random_material());
world.push(Sphere::xyz(1., 0., -1.0, 0.5, right.clone())); // }
world.push(Sphere::xyz(0., 0.7, -0.4, 0.2, blue.clone())); //
// let mut world = vec![Sphere::xyz(0., -1000.5, -1., 1000., materials[0].clone())];
let c = Camera::new(16. / 9., 1920, 3, 50); // for i in 0..40 {
c.render(&world); // info!("Generating {}th sphere.", i + 1);
// world.push(random_sphere_on_floor(
// &materials,
// &world,
// ((i + 1) as f32).ln() + (15. as f32).log(10.),
// ));
// }
//
// // let mut c = Camera::new(400, 300);
// let mut c = Camera::new(1920, 1080);
// c.set_fov(60.);
// c.set_anti_alias_rate(1);
// c.set_max_depth(10);
// c.set_look_from(Vec3::new(-60., 10., 1.));
// c.set_look_at(Vec3::new(0., 0., 0.));
// c.render(&world);
} }

View File

@@ -45,7 +45,7 @@ impl Hit {
self.front_face self.front_face
} }
pub fn hit_list<T: Hittable>(hittables: &Vec<T>, r: &Ray) -> Option<Hit> { pub fn hit_list(hittables: &Vec<Arc<dyn Hittable>>, r: &Ray) -> Option<Hit> {
let mut closest: Option<Hit> = None; let mut closest: Option<Hit> = None;
for hittable in hittables { for hittable in hittables {
if let Some(hit) = hittable.hit(r) { if let Some(hit) = hittable.hit(r) {

View File

@@ -1,6 +1,7 @@
use core::f32::math::sqrt; use core::f32::math::sqrt;
use rand::RngExt; use rand::RngExt;
use serde::Deserialize;
use crate::{ use crate::{
objects::{hit::Hit, materials::traits::Material}, objects::{hit::Hit, materials::traits::Material},
@@ -8,6 +9,7 @@ use crate::{
vec3::{Colour, Vec3}, vec3::{Colour, Vec3},
}; };
#[derive(Debug, Deserialize)]
pub struct Dielectric { pub struct Dielectric {
refraction_index: f32, refraction_index: f32,
} }

View File

@@ -1,4 +1,5 @@
use rand::RngExt; use rand::RngExt;
use serde::Deserialize;
use crate::{ use crate::{
objects::{hit::Hit, materials::traits::Material}, objects::{hit::Hit, materials::traits::Material},
@@ -6,6 +7,7 @@ use crate::{
vec3::{Colour, Vec3}, vec3::{Colour, Vec3},
}; };
#[derive(Debug, Deserialize)]
pub struct Lambertian { pub struct Lambertian {
albedo: Colour, albedo: Colour,
prob: f32, prob: f32,
@@ -42,6 +44,7 @@ impl Material for Lambertian {
} }
} }
#[derive(Debug, Deserialize)]
pub struct Metal { pub struct Metal {
albedo: Colour, albedo: Colour,
prob: f32, prob: f32,

View File

@@ -1,5 +1,6 @@
use crate::{objects::hit::Hit, ray::Ray, vec3::Colour}; use crate::{objects::hit::Hit, ray::Ray, vec3::Colour};
use std::fmt::Debug;
pub trait Material { pub trait Material: Debug {
fn scatter(&self, hit: &Hit, ray: &Ray) -> Option<(Ray, Colour)>; fn scatter(&self, hit: &Hit, ray: &Ray) -> Option<(Ray, Colour)>;
} }

View File

@@ -1,6 +1,9 @@
use core::f32::math::sqrt; use core::f32::math::sqrt;
use std::fmt::{self, Debug};
use std::sync::Arc; use std::sync::Arc;
use serde::Deserialize;
use crate::objects::hit::Hit; use crate::objects::hit::Hit;
use crate::objects::materials::traits::Material; use crate::objects::materials::traits::Material;
use crate::objects::traits::Hittable; use crate::objects::traits::Hittable;
@@ -13,6 +16,16 @@ pub struct Sphere {
material: Arc<dyn Material>, material: Arc<dyn Material>,
} }
impl Debug for Sphere {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("Sphere")
.field("center", &self.center)
.field("radius", &self.radius)
.field("material", &self.material)
.finish()
}
}
impl Sphere { impl Sphere {
pub fn new(center: Vec3, r: f32, mat: Arc<dyn Material>) -> Self { pub fn new(center: Vec3, r: f32, mat: Arc<dyn Material>) -> Self {
Self { Self {
@@ -29,6 +42,16 @@ impl Sphere {
material: mat, material: mat,
} }
} }
pub fn center(&self) -> &Vec3 {
&self.center
}
pub fn set_center(&mut self, center: Vec3) {
if self.center != center {
self.center = center;
}
}
} }
impl Hittable for Sphere { impl Hittable for Sphere {
@@ -60,4 +83,12 @@ impl Hittable for Sphere {
fn normal_at(&self, p: &Vec3) -> Vec3 { fn normal_at(&self, p: &Vec3) -> Vec3 {
(*p - self.center).get_unit() (*p - self.center).get_unit()
} }
fn inside(&self, p: &Vec3) -> bool {
(*p - self.center).length() < self.radius
}
fn closest_on_surface(&self, p: &Vec3) -> Vec3 {
self.normal_at(p) * self.radius
}
} }

View File

@@ -1,8 +1,13 @@
use std::fmt::Debug;
use crate::objects::hit::Hit; use crate::objects::hit::Hit;
use crate::Ray; use crate::Ray;
use crate::Vec3; use crate::Vec3;
pub trait Hittable { pub trait Hittable: Debug {
fn hit(&self, r: &Ray) -> Option<Hit>; fn hit(&self, r: &Ray) -> Option<Hit>;
fn normal_at(&self, p: &Vec3) -> Vec3; fn normal_at(&self, p: &Vec3) -> Vec3;
// fn intersect<H: Hittable>(&self, o: &H) -> bool;
fn inside(&self, p: &Vec3) -> bool;
fn closest_on_surface(&self, p: &Vec3) -> Vec3;
} }

2
src/scenes.rs Normal file
View File

@@ -0,0 +1,2 @@
pub mod raw_camera;
pub mod scene;

57
src/scenes/raw_camera.rs Normal file
View File

@@ -0,0 +1,57 @@
use serde::Deserialize;
use crate::{camera::Camera, vec3::Vec3};
#[derive(Deserialize)]
pub struct RawCamera {
// output
image_width: u32, // TODO: test these are now explicitly required (and that default impl does
// not make these optional)
image_height: u32,
// raytracing
#[serde(default)]
anti_alias_rate: u32,
#[serde(default)]
max_depth: u32,
// camera
#[serde(default)]
fov: f32,
#[serde(default)]
look_from: Vec3,
#[serde(default)]
look_at: Vec3,
#[serde(default)]
vup: Vec3,
}
impl Default for RawCamera {
fn default() -> Self {
Self {
image_width: 400,
image_height: 300,
anti_alias_rate: 1,
max_depth: 10,
fov: 70.,
look_from: Vec3::new(0., 0., 0.),
look_at: Vec3::new(0., 0., -1.),
vup: Vec3::new(0., 1., 0.),
}
}
}
impl From<RawCamera> for Camera {
fn from(raw: RawCamera) -> Self {
Camera::new_full(
raw.image_width,
raw.image_height,
raw.anti_alias_rate,
raw.max_depth,
raw.fov,
raw.look_from,
raw.look_at,
raw.vup,
)
}
}

115
src/scenes/scene.rs Normal file
View File

@@ -0,0 +1,115 @@
use std::sync::Arc;
use log::warn;
use serde::Deserialize;
use crate::{
camera::Camera,
objects::{
materials::{
dielectric::Dielectric,
lambertian::{Lambertian, Metal},
traits::Material,
},
sphere::Sphere,
traits::Hittable,
},
scenes::raw_camera::RawCamera,
vec3::Vec3,
};
pub struct Scene {
pub camera: Camera,
pub materials: Vec<Arc<dyn Material>>,
pub objects: Vec<Arc<dyn Hittable>>,
}
impl Scene {
pub fn render(&mut self) {
self.camera.render(&self.objects);
}
}
#[derive(Deserialize)]
struct SceneDef {
pub camera: RawCamera,
pub materials: Vec<MaterialDef>,
pub objects: Vec<HittableDef>,
}
impl<'de> Deserialize<'de> for Scene {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let conc = SceneDef::deserialize(deserializer)?;
let mats: Vec<Arc<dyn Material>> = conc
.materials
.into_iter()
.map(MaterialDef::into_arc)
.collect();
let objs: Vec<Arc<dyn Hittable>> = conc
.objects
.into_iter()
.filter_map(|h| h.into_arc(&mats))
.collect();
Ok(Self {
camera: Camera::from(conc.camera),
materials: mats,
objects: objs,
})
}
}
#[derive(Deserialize)]
struct RawSphere {
pub center: Vec3,
pub radius: f32,
pub material: u32,
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
enum HittableDef {
Sphere(RawSphere),
}
impl HittableDef {
fn into_arc(self, materials: &Vec<Arc<dyn Material>>) -> Option<Arc<dyn Hittable>> {
// THOUGHT: i think this can be done better; in the map/filter call up there?
match self {
HittableDef::Sphere(s) => {
if s.material as usize >= materials.len() {
warn!(
"Sphere specified nonexistent material {}; skipping...",
s.material
);
return None;
}
Some(Arc::new(Sphere::new(
s.center,
s.radius,
materials.get(s.material as usize).unwrap().clone(),
)))
}
}
}
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
enum MaterialDef {
Lambertian(Lambertian),
Metal(Metal),
Dielectric(Dielectric),
}
impl MaterialDef {
fn into_arc(self) -> Arc<dyn Material> {
match self {
MaterialDef::Lambertian(l) => Arc::new(l),
MaterialDef::Metal(m) => Arc::new(m),
MaterialDef::Dielectric(d) => Arc::new(d),
}
}
}

View File

@@ -1,39 +1,40 @@
use core::f32::math::sqrt; use core::f32::math::sqrt;
use is_close::default; use is_close::default;
use rand::RngExt; use rand::RngExt;
use serde::Deserialize;
use std::{ use std::{
fmt::Display, fmt::Display,
ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign}, ops::{Add, AddAssign, Div, DivAssign, Mul, MulAssign, Neg, Sub, SubAssign},
}; };
#[derive(Copy, Clone)] #[derive(Copy, Clone, Debug, Deserialize)]
pub struct Vec3 { pub struct Vec3 {
r: f32, x: f32,
g: f32, y: f32,
b: f32, z: f32,
} }
pub type Colour = Vec3; pub type Colour = Vec3;
impl Vec3 { impl Vec3 {
pub fn new(r: f32, g: f32, b: f32) -> Self { pub fn new(r: f32, g: f32, b: f32) -> Self {
Self { r: r, g: g, b: b } Self { x: r, y: g, z: b }
} }
pub fn nil() -> Self { pub fn nil() -> Self {
Self { Self {
r: 0., x: 0.,
g: 0., y: 0.,
b: 0., z: 0.,
} }
} }
pub fn random() -> Self { pub fn random() -> Self {
let mut rng = rand::rng(); let mut rng = rand::rng();
Self { Self {
r: rng.random_range(-1.0..1.), x: rng.random_range(-1.0..1.),
g: rng.random_range(-1.0..1.), y: rng.random_range(-1.0..1.),
b: rng.random_range(-1.0..1.), z: rng.random_range(-1.0..1.),
} }
} }
@@ -56,15 +57,15 @@ impl Vec3 {
} }
pub fn x(&self) -> &f32 { pub fn x(&self) -> &f32 {
&self.r &self.x
} }
pub fn y(&self) -> &f32 { pub fn y(&self) -> &f32 {
&self.g &self.y
} }
pub fn z(&self) -> &f32 { pub fn z(&self) -> &f32 {
&self.b &self.z
} }
pub fn length(&self) -> f32 { pub fn length(&self) -> f32 {
@@ -72,18 +73,18 @@ impl Vec3 {
} }
pub fn length_squared(&self) -> f32 { pub fn length_squared(&self) -> f32 {
(self.r * self.r + self.g * self.g + self.b * self.b) as f32 (self.x * self.x + self.y * self.y + self.z * self.z) as f32
} }
pub fn dot(&self, other: &Self) -> f32 { pub fn dot(&self, other: &Self) -> f32 {
self.r * other.r + self.g * other.g + self.b * other.b self.x * other.x + self.y * other.y + self.z * other.z
} }
pub fn cross(&self, other: &Self) -> Self { pub fn cross(&self, other: &Self) -> Self {
Self { Self {
r: self.g * other.b - self.b * other.g, x: self.y * other.z - self.z * other.y,
g: self.b * other.r - self.r * other.b, y: self.z * other.x - self.x * other.z,
b: self.r * other.g - self.g * other.r, z: self.x * other.y - self.y * other.x,
} }
} }
@@ -96,9 +97,9 @@ impl Vec3 {
} }
pub fn near_zero(&self) -> bool { pub fn near_zero(&self) -> bool {
default().is_close(self.r, 0.) default().is_close(self.x, 0.)
&& default().is_close(self.g, 0.) && default().is_close(self.y, 0.)
&& default().is_close(self.b, 0.) && default().is_close(self.z, 0.)
} }
pub fn reflect(&self, n: &Self) -> Self { pub fn reflect(&self, n: &Self) -> Self {
@@ -117,18 +118,18 @@ impl Vec3 {
pub fn output(self) -> image::Rgb<u8> { pub fn output(self) -> image::Rgb<u8> {
// gamma correction // gamma correction
let r = if self.r > 0. { let r = if self.x > 0. {
sqrt(self.r).clamp(0., 1.) sqrt(self.x).clamp(0., 1.)
} else { } else {
0. 0.
}; };
let g = if self.g > 0. { let g = if self.y > 0. {
sqrt(self.g).clamp(0., 1.) sqrt(self.y).clamp(0., 1.)
} else { } else {
0. 0.
}; };
let b = if self.b > 0. { let b = if self.z > 0. {
sqrt(self.b).clamp(0.1, 1.) sqrt(self.z).clamp(0.1, 1.)
} else { } else {
0. 0.
}; };
@@ -142,9 +143,19 @@ impl Vec3 {
pub fn clone(&self) -> Self { pub fn clone(&self) -> Self {
Self { Self {
r: self.r, x: self.x,
g: self.g, y: self.y,
b: self.b, z: self.z,
}
}
}
impl Default for Vec3 {
fn default() -> Self {
Self {
x: 0.,
y: 0.,
z: 0.,
} }
} }
} }
@@ -162,9 +173,9 @@ impl Neg for Vec3 {
fn neg(self) -> Self::Output { fn neg(self) -> Self::Output {
Self { Self {
r: -self.r, x: -self.x,
g: -self.g, y: -self.y,
b: -self.b, z: -self.z,
} }
} }
} }
@@ -174,9 +185,9 @@ impl Add<Vec3> for Vec3 {
fn add(self, _rhs: Self) -> Self { fn add(self, _rhs: Self) -> Self {
Self { Self {
r: self.r + _rhs.r, x: self.x + _rhs.x,
g: self.g + _rhs.g, y: self.y + _rhs.y,
b: self.b + _rhs.b, z: self.z + _rhs.z,
} }
} }
} }
@@ -186,9 +197,9 @@ impl Add<f32> for Vec3 {
fn add(self, f: f32) -> Self { fn add(self, f: f32) -> Self {
Self { Self {
r: self.r + f, x: self.x + f,
g: self.g + f, y: self.y + f,
b: self.b + f, z: self.z + f,
} }
} }
} }
@@ -196,9 +207,9 @@ impl Add<f32> for Vec3 {
impl AddAssign<Vec3> for Vec3 { impl AddAssign<Vec3> for Vec3 {
fn add_assign(&mut self, other: Self) { fn add_assign(&mut self, other: Self) {
*self = Self { *self = Self {
r: self.r + other.r, x: self.x + other.x,
g: self.g + other.g, y: self.y + other.y,
b: self.b + other.b, z: self.z + other.z,
}; };
} }
} }
@@ -206,9 +217,9 @@ impl AddAssign<Vec3> for Vec3 {
impl AddAssign<f32> for Vec3 { impl AddAssign<f32> for Vec3 {
fn add_assign(&mut self, f: f32) { fn add_assign(&mut self, f: f32) {
*self = Self { *self = Self {
r: self.r + f, x: self.x + f,
g: self.g + f, y: self.y + f,
b: self.b + f, z: self.z + f,
}; };
} }
} }
@@ -217,9 +228,9 @@ impl Sub<&Vec3> for Vec3 {
fn sub(self, rhs: &Self) -> Self { fn sub(self, rhs: &Self) -> Self {
Self { Self {
r: self.r - rhs.r, x: self.x - rhs.x,
g: self.g - rhs.g, y: self.y - rhs.y,
b: self.b - rhs.b, z: self.z - rhs.z,
} }
} }
} }
@@ -229,9 +240,9 @@ impl Sub<Vec3> for Vec3 {
fn sub(self, rhs: Self) -> Self { fn sub(self, rhs: Self) -> Self {
Self { Self {
r: self.r - rhs.r, x: self.x - rhs.x,
g: self.g - rhs.g, y: self.y - rhs.y,
b: self.b - rhs.b, z: self.z - rhs.z,
} }
} }
} }
@@ -241,9 +252,9 @@ impl Sub<f32> for Vec3 {
fn sub(self, f: f32) -> Self { fn sub(self, f: f32) -> Self {
Self { Self {
r: self.r - f, x: self.x - f,
g: self.g - f, y: self.y - f,
b: self.b - f, z: self.z - f,
} }
} }
} }
@@ -251,9 +262,9 @@ impl Sub<f32> for Vec3 {
impl SubAssign<Vec3> for Vec3 { impl SubAssign<Vec3> for Vec3 {
fn sub_assign(&mut self, rhs: Self) { fn sub_assign(&mut self, rhs: Self) {
*self = Self { *self = Self {
r: self.r - rhs.r, x: self.x - rhs.x,
g: self.g - rhs.g, y: self.y - rhs.y,
b: self.b - rhs.b, z: self.z - rhs.z,
}; };
} }
} }
@@ -261,9 +272,9 @@ impl SubAssign<Vec3> for Vec3 {
impl SubAssign<f32> for Vec3 { impl SubAssign<f32> for Vec3 {
fn sub_assign(&mut self, f: f32) { fn sub_assign(&mut self, f: f32) {
*self = Self { *self = Self {
r: self.r - f, x: self.x - f,
g: self.g - f, y: self.y - f,
b: self.b - f, z: self.z - f,
}; };
} }
} }
@@ -273,9 +284,9 @@ impl Mul<Vec3> for Vec3 {
fn mul(self, rhs: Self) -> Self::Output { fn mul(self, rhs: Self) -> Self::Output {
Self { Self {
r: self.r * rhs.r, x: self.x * rhs.x,
g: self.g * rhs.g, y: self.y * rhs.y,
b: self.b * rhs.b, z: self.z * rhs.z,
} }
} }
} }
@@ -286,9 +297,9 @@ impl Mul<Vec3> for u32 {
fn mul(self, rhs: Vec3) -> Self::Output { fn mul(self, rhs: Vec3) -> Self::Output {
let f = self as f32; let f = self as f32;
Vec3 { Vec3 {
r: rhs.r * f, x: rhs.x * f,
g: rhs.g * f, y: rhs.y * f,
b: rhs.b * f, z: rhs.z * f,
} }
} }
} }
@@ -298,9 +309,9 @@ impl Mul<f32> for Vec3 {
fn mul(self, f: f32) -> Self::Output { fn mul(self, f: f32) -> Self::Output {
Self { Self {
r: self.r * f, x: self.x * f,
g: self.g * f, y: self.y * f,
b: self.b * f, z: self.z * f,
} }
} }
} }
@@ -310,9 +321,9 @@ impl Mul<Vec3> for f32 {
fn mul(self, rhs: Vec3) -> Self::Output { fn mul(self, rhs: Vec3) -> Self::Output {
Vec3 { Vec3 {
r: rhs.r * self, x: rhs.x * self,
g: rhs.g * self, y: rhs.y * self,
b: rhs.b * self, z: rhs.z * self,
} }
} }
} }
@@ -322,9 +333,9 @@ impl Mul<&Vec3> for f32 {
fn mul(self, rhs: &Vec3) -> Self::Output { fn mul(self, rhs: &Vec3) -> Self::Output {
Vec3 { Vec3 {
r: rhs.r * self, x: rhs.x * self,
g: rhs.g * self, y: rhs.y * self,
b: rhs.b * self, z: rhs.z * self,
} }
} }
} }
@@ -332,9 +343,9 @@ impl Mul<&Vec3> for f32 {
impl MulAssign<Vec3> for Vec3 { impl MulAssign<Vec3> for Vec3 {
fn mul_assign(&mut self, rhs: Self) { fn mul_assign(&mut self, rhs: Self) {
*self = Self { *self = Self {
r: self.r * rhs.r, x: self.x * rhs.x,
g: self.g * rhs.g, y: self.y * rhs.y,
b: self.b * rhs.b, z: self.z * rhs.z,
}; };
} }
} }
@@ -342,9 +353,9 @@ impl MulAssign<Vec3> for Vec3 {
impl MulAssign<f32> for Vec3 { impl MulAssign<f32> for Vec3 {
fn mul_assign(&mut self, f: f32) { fn mul_assign(&mut self, f: f32) {
*self = Self { *self = Self {
r: self.r * f, x: self.x * f,
g: self.g * f, y: self.y * f,
b: self.b * f, z: self.z * f,
}; };
} }
} }
@@ -354,9 +365,9 @@ impl Div<Vec3> for Vec3 {
fn div(self, rhs: Self) -> Self::Output { fn div(self, rhs: Self) -> Self::Output {
Self { Self {
r: self.r / rhs.r, x: self.x / rhs.x,
g: self.g / rhs.g, y: self.y / rhs.y,
b: self.b / rhs.b, z: self.z / rhs.z,
} }
} }
} }
@@ -366,9 +377,9 @@ impl Div<i32> for Vec3 {
fn div(self, i: i32) -> Self::Output { fn div(self, i: i32) -> Self::Output {
let f: f32 = i as f32; let f: f32 = i as f32;
Self { Self {
r: self.r / f, x: self.x / f,
g: self.g / f, y: self.y / f,
b: self.b / f, z: self.z / f,
} }
} }
} }
@@ -378,9 +389,9 @@ impl Div<f32> for Vec3 {
fn div(self, f: f32) -> Self::Output { fn div(self, f: f32) -> Self::Output {
Self { Self {
r: self.r / f, x: self.x / f,
g: self.g / f, y: self.y / f,
b: self.b / f, z: self.z / f,
} }
} }
} }
@@ -388,9 +399,9 @@ impl Div<f32> for Vec3 {
impl DivAssign<Vec3> for Vec3 { impl DivAssign<Vec3> for Vec3 {
fn div_assign(&mut self, rhs: Self) { fn div_assign(&mut self, rhs: Self) {
*self = Self { *self = Self {
r: self.r / rhs.r, x: self.x / rhs.x,
g: self.g / rhs.g, y: self.y / rhs.y,
b: self.b / rhs.b, z: self.z / rhs.z,
}; };
} }
} }
@@ -398,21 +409,21 @@ impl DivAssign<Vec3> for Vec3 {
impl DivAssign<f32> for Vec3 { impl DivAssign<f32> for Vec3 {
fn div_assign(&mut self, f: f32) { fn div_assign(&mut self, f: f32) {
*self = Self { *self = Self {
r: self.r / f, x: self.x / f,
g: self.g / f, y: self.y / f,
b: self.b / f, z: self.z / f,
}; };
} }
} }
impl PartialEq for Vec3 { impl PartialEq for Vec3 {
fn eq(&self, other: &Self) -> bool { fn eq(&self, other: &Self) -> bool {
self.r == other.r && self.g == other.g && self.b == other.b self.x == other.x && self.y == other.y && self.z == other.z
} }
} }
impl Display for Vec3 { impl Display for Vec3 {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "({}, {}, {})", self.r, self.g, self.b) write!(f, "({}, {}, {})", self.x, self.y, self.z)
} }
} }