diff options
author | Julian T <julian@jtle.dk> | 2021-08-05 15:44:40 +0200 |
---|---|---|
committer | Julian T <julian@jtle.dk> | 2021-08-05 15:44:40 +0200 |
commit | 3ef8f4d918406eec6bdc29e0ebd883fabfac9b2e (patch) | |
tree | aa4b1aac1e165821c16f222ebfb9212a9740e98b | |
parent | 45119506c0293fdde6cef35f6e6f82d4055b46b6 (diff) |
Add picture for c5505ab84820248c6dba35fc06aef9e0ced183derendered
37 files changed, 0 insertions, 2536 deletions
diff --git a/Cargo.lock b/Cargo.lock deleted file mode 100644 index 370504f..0000000 --- a/Cargo.lock +++ /dev/null @@ -1,436 +0,0 @@ -# This file is automatically @generated by Cargo. -# It is not intended for manual editing. -[[package]] -name = "adler" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ee2a4ec343196209d6594e19543ae87a39f96d5534d7174822a3ad825dd6ed7e" - -[[package]] -name = "adler32" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aae1277d39aeec15cb388266ecc24b11c80469deae6067e17a1a7aa9e5c1f234" - -[[package]] -name = "autocfg" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" - -[[package]] -name = "bitflags" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" - -[[package]] -name = "bytemuck" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a4bad0c5981acc24bc09e532f35160f952e35422603f0563cd7a73c2c2e65a0" - -[[package]] -name = "byteorder" -version = "1.4.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ae44d1a3d5a19df61dd0c8beb138458ac2a53a7ac09eba97d55592540004306b" - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "color_quant" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3d7b894f5411737b7867f4827955924d7c254fc9f4d91a6aad6b097804b1018b" - -[[package]] -name = "const_fn" -version = "0.4.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "28b9d6de7f49e22cf97ad17fc4036ece69300032f45f78f30b4a4482cdc3f4a6" - -[[package]] -name = "crc32fast" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81156fece84ab6a9f2afdb109ce3ae577e42b1228441eded99bd77f627953b1a" -dependencies = [ - "cfg-if", -] - -[[package]] -name = "crossbeam" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd01a6eb3daaafa260f6fc94c3a6c36390abc2080e38e3e34ced87393fb77d80" -dependencies = [ - "cfg-if", - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-epoch", - "crossbeam-queue", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-channel" -version = "0.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dca26ee1f8d361640700bde38b2c37d8c22b3ce2d360e1fc1c74ea4b0aa7d775" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-deque" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94af6efb46fef72616855b036a624cf27ba656ffc9be1b9a3c931cfc7749a9a9" -dependencies = [ - "cfg-if", - "crossbeam-epoch", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-epoch" -version = "0.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1aaa739f95311c2c7887a76863f500026092fb1dce0161dab577e559ef3569d" -dependencies = [ - "cfg-if", - "const_fn", - "crossbeam-utils", - "lazy_static", - "memoffset", - "scopeguard", -] - -[[package]] -name = "crossbeam-queue" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f6cb3c7f5b8e51bc3ebb73a2327ad4abdbd119dc13223f14f961d2f38486756" -dependencies = [ - "cfg-if", - "crossbeam-utils", -] - -[[package]] -name = "crossbeam-utils" -version = "0.8.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02d96d1e189ef58269ebe5b97953da3274d83a93af647c2ddd6f9dab28cedb8d" -dependencies = [ - "autocfg", - "cfg-if", - "lazy_static", -] - -[[package]] -name = "deflate" -version = "0.8.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73770f8e1fe7d64df17ca66ad28994a0a623ea497fa69486e14984e715c5d174" -dependencies = [ - "adler32", - "byteorder", -] - -[[package]] -name = "either" -version = "1.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e78d4f1cc4ae33bbfc157ed5d5a5ef3bc29227303d595861deb238fcec4e9457" - -[[package]] -name = "getrandom" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c9495705279e7140bf035dde1f6e750c162df8b625267cd52cc44e0b156732c8" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gif" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "02efba560f227847cb41463a7395c514d127d4f74fff12ef0137fff1b84b96c4" -dependencies = [ - "color_quant", - "weezl", -] - -[[package]] -name = "hermit-abi" -version = "0.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "322f4de77956e22ed0e5032c359a0f1273f1f7f0d79bfa3b8ffbc730d7fbcc5c" -dependencies = [ - "libc", -] - -[[package]] -name = "image" -version = "0.23.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ce04077ead78e39ae8610ad26216aed811996b043d47beed5090db674f9e9b5" -dependencies = [ - "bytemuck", - "byteorder", - "color_quant", - "gif", - "jpeg-decoder", - "num-iter", - "num-rational", - "num-traits", - "png", - "scoped_threadpool", - "tiff", -] - -[[package]] -name = "jpeg-decoder" -version = "0.1.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "229d53d58899083193af11e15917b5640cd40b29ff475a1fe4ef725deb02d0f2" -dependencies = [ - "rayon", -] - -[[package]] -name = "lazy_static" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" - -[[package]] -name = "libc" -version = "0.2.85" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7ccac4b00700875e6a07c6cde370d44d32fa01c5a65cdd2fca6858c479d28bb3" - -[[package]] -name = "memoffset" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "157b4208e3059a8f9e78d559edc658e13df41410cb3ae03979c83130067fdd87" -dependencies = [ - "autocfg", -] - -[[package]] -name = "miniz_oxide" -version = "0.3.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "791daaae1ed6889560f8c4359194f56648355540573244a5448a83ba1ecc7435" -dependencies = [ - "adler32", -] - -[[package]] -name = "miniz_oxide" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f2d26ec3309788e423cfbf68ad1800f061638098d76a83681af979dc4eda19d" -dependencies = [ - "adler", - "autocfg", -] - -[[package]] -name = "num-integer" -version = "0.1.44" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d2cc698a63b549a70bc047073d2949cce27cd1c7b0a4a862d08a8031bc2801db" -dependencies = [ - "autocfg", - "num-traits", -] - -[[package]] -name = "num-iter" -version = "0.1.42" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b2021c8337a54d21aca0d59a92577a029af9431cb59b909b03252b9c164fad59" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-rational" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "12ac428b1cb17fce6f731001d307d351ec70a6d202fc2e60f7d4c5e42d8f4f07" -dependencies = [ - "autocfg", - "num-integer", - "num-traits", -] - -[[package]] -name = "num-traits" -version = "0.2.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a64b1ec5cda2586e284722486d802acf1f7dbdc623e2bfc57e65ca1cd099290" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_cpus" -version = "1.13.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "05499f3756671c15885fee9034446956fff3f243d6077b91e5767df161f766b3" -dependencies = [ - "hermit-abi", - "libc", -] - -[[package]] -name = "png" -version = "0.16.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c3287920cb847dee3de33d301c463fba14dda99db24214ddf93f83d3021f4c6" -dependencies = [ - "bitflags", - "crc32fast", - "deflate", - "miniz_oxide 0.3.7", -] - -[[package]] -name = "ppv-lite86" -version = "0.2.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857" - -[[package]] -name = "rand" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef9e7e66b4468674bfcb0c81af8b7fa0bb154fa9f28eb840da5c447baeb8d7e" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", - "rand_hc", -] - -[[package]] -name = "rand_chacha" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e12735cf05c9e10bf21534da50a147b924d555dc7a547c42e6bb2d5b6017ae0d" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c026d7df8b298d90ccbbc5190bd04d85e159eaf5576caeacf8741da93ccbd2e5" -dependencies = [ - "getrandom", -] - -[[package]] -name = "rand_hc" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3190ef7066a446f2e7f42e239d161e905420ccab01eb967c9eb27d21b2322a73" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rand_pcg" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7de198537002b913568a3847e53535ace266f93526caf5c360ec41d72c5787f0" -dependencies = [ - "rand_core", -] - -[[package]] -name = "rayon" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b0d8e0819fadc20c74ea8373106ead0600e3a67ef1fe8da56e39b9ae7275674" -dependencies = [ - "autocfg", - "crossbeam-deque", - "either", - "rayon-core", -] - -[[package]] -name = "rayon-core" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9ab346ac5921dc62ffa9f89b7a773907511cdfa5490c572ae9be1be33e8afa4a" -dependencies = [ - "crossbeam-channel", - "crossbeam-deque", - "crossbeam-utils", - "lazy_static", - "num_cpus", -] - -[[package]] -name = "rendering" -version = "0.1.0" -dependencies = [ - "crossbeam", - "image", - "rand", - "rand_pcg", -] - -[[package]] -name = "scoped_threadpool" -version = "0.1.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1d51f5df5af43ab3f1360b429fa5e0152ac5ce8c0bd6485cae490332e96846a8" - -[[package]] -name = "scopeguard" -version = "1.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" - -[[package]] -name = "tiff" -version = "0.6.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a53f4706d65497df0c4349241deddf35f84cee19c87ed86ea8ca590f4464437" -dependencies = [ - "jpeg-decoder", - "miniz_oxide 0.4.3", - "weezl", -] - -[[package]] -name = "wasi" -version = "0.10.2+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6" - -[[package]] -name = "weezl" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4a32b378380f4e9869b22f0b5177c68a5519f03b3454fde0b291455ddbae266c" diff --git a/Cargo.toml b/Cargo.toml deleted file mode 100644 index 629e22f..0000000 --- a/Cargo.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "rendering" -version = "0.1.0" -authors = ["Julian T <julian@jtle.dk>"] -edition = "2018" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html - -[dependencies] -image = "0.23.12" -rand = "0.8.3" -rand_pcg = "0.3.0" -crossbeam = "0.8.0" diff --git a/generated.png b/generated.png Binary files differindex 946d096..8e36d3b 100644 --- a/generated.png +++ b/generated.png diff --git a/readme.md b/readme.md deleted file mode 100644 index b4691be..0000000 --- a/readme.md +++ /dev/null @@ -1,25 +0,0 @@ -# Rendering in rust - -This is a (not finished) ray tracer written in rust. -It is based on the book serie [Ray Tracing In One Weekend](https://raytracing.github.io/) and [Physics Based Rendering](http://www.pbr-book.org/). - -This rust version is my third implementation, the previous being: - - -- [Raytracing in c](https://git.jtle.dk/raytrace/about/) - This is the one which gives the best results, - however as my ambitions for the project grew as did my requirement for more language features. -- [Pathtracing in c++](https://github.com/jbjjbjjbj/rendering/tree/old_master) -I never really finished it before changing to rust. - However it had a really cool qt gui, updating as samples were added. - -## Rendered image - -![render](https://raw.githubusercontent.com/jbjjbjjbj/rendering/rendered/generated.png) - -## Goals - -- [X] Render collection of circles outside, with blurry background -- [ ] Render [Cornell box](https://en.wikipedia.org/wiki/Cornell_box) -- [ ] Render [Utah teapot](https://en.wikipedia.org/wiki/Utah_teapot) -- [ ] Render [Stanford dragon](https://en.wikipedia.org/wiki/Stanford_dragon) diff --git a/src/camera/camera.rs b/src/camera/camera.rs deleted file mode 100644 index 7f9e79d..0000000 --- a/src/camera/camera.rs +++ /dev/null @@ -1,135 +0,0 @@ -//! Generates rays from screen coordinates -//! -//! Generates rays in world space from screen coordinates. -//! Future versions should also simulate depth of field. -//! -//! # Examples -//! -//! ``` -//! use rendering::camera::{CameraSettings, Camera}; -//! use rendering::core::{Vector3f, Vector2f, Vector2i}; -//! use rendering::sample::UniformSampler; -//! -//! let set = CameraSettings { -//! origin: Vector3f::new(10.0), -//! target: Vector3f::new(0.0), -//! up: Vector3f::new_xyz(0.0, 1.0, 0.0), -//! fov: 90.0, -//! filmsize: Vector2i::new(10), -//! focus: None, -//! aperture: None, -//! }; -//! -//! let cam = Camera::new(&set); -//! let mut sampler = UniformSampler::new(); -//! -//! let (r, _) = cam.generate_ray(&Vector2f::new(5.0), &mut sampler); -//! let dir = r.direction; -//! -//! assert!( -//! dir.x == -0.6031558065478413 && -//! dir.y == -0.6599739684616743 && -//! dir.z == -0.4479257014065748 -//! ); -//! -//! ``` -use crate::Float; -use crate::core::{Vector3f, Vector2f, Vector2i, Ray}; -use crate::sample::Sampler; - -/// A simple perspective camera -pub struct Camera { - /// The camera origin in the screen - origin: Vector3f, - /// Vector from camera origin to the screen lower left corner of the film plane - screen_origin: Vector3f, - /// Scaling vectors from screen_origin - qx: Vector3f, - qy: Vector3f, - - /// Value for depth of view - lens_radius: Option<Float>, -} - -/// Settings for initializing camera -pub struct CameraSettings { - /// Where rays originate from - pub origin: Vector3f, - /// Point where center of image is pointed at - pub target: Vector3f, - /// Vector that will be up in the resulting image - pub up: Vector3f, - /// The vertical field of view in degrees. - /// Currently must be between [0; 180[. - pub fov: Float, - /// The film aspect ratio, height / width - pub filmsize: Vector2i, - /// The lens aperture - /// - /// Depth of view is disabled if None - pub aperture: Option<Float>, - /// The distance to the focus plane - /// - /// if None it will be set to the distance between origin and target - pub focus: Option<Float>, -} - -impl Camera { - /// Create a new camera look at a target - pub fn new(set: &CameraSettings) -> Camera { - let filmsize = Vector2f::from(set.filmsize); - // Calculate translation vectors - let mut forward = set.target - set.origin; - - let focus = set.focus.unwrap_or_else(|| forward.length()); - - forward.norm_in(); - - let right = set.up.cross(&forward).norm(); - let newup = forward.cross(&right).norm(); - - let aspect = (filmsize.y) / (filmsize.x); - // Calculate screen size from fov and focus distance - let width = 2.0 * focus * (set.fov / 2.0).to_radians().tan(); - let height = aspect * width; - - // Calculate screen scaling vectors - let qx = right * (width / (filmsize.x - 1.0)); - let qy = newup * (height / (filmsize.y - 1.0)); - - let screen_origin = forward * focus - (right * (width/2.0)) + (newup * (height/2.0)); - - Camera { - origin: set.origin, - screen_origin, - qx, - qy, - lens_radius: set.aperture.map(|a| a / 2.0), - } - } - - /// Generates a ray a screen space point - /// - /// The point coordinates should be between [0,1) with (0, 0) being the upper left corner - /// - /// Will return a ray and a weight - /// - /// The direction of the returned way is normalized - pub fn generate_ray(&self, point: &Vector2f, sampler: &mut dyn Sampler) -> (Ray, Float) { - // Depth of view origin offset - let ooffset = match self.lens_radius { - Some(r) => { - let rand_dir = sampler.get_in_circle() * r; - self.qx * rand_dir.x + self.qy * rand_dir.y - }, - None => Vector3f::ZERO, - }; - - let dir = self.screen_origin + (self.qx * point.x) - (self.qy * point.y) - ooffset; - - ( - Ray { origin: self.origin + ooffset, direction: dir.norm() }, - 1.0 - ) - } -} diff --git a/src/camera/film.rs b/src/camera/film.rs deleted file mode 100644 index 852ae9e..0000000 --- a/src/camera/film.rs +++ /dev/null @@ -1,145 +0,0 @@ -use crate::core::*; -use crate::Float; -use image::{RgbImage, Rgb}; - -/// Contains the necesary values when doing calculations -/// -/// This is not the final RGB value -#[derive(Clone)] -pub struct Pixel { - /// The sum of the collected samples - rgb: Spectrum, - /// The amount of samples collected - samples: u32, -} - -pub struct Film { - pub size: Vector2i, - pub frame: Bound2i, - - pixels: Vec<Pixel>, -} - -/// FilmTile is a small version of the Film used when rendering -/// -/// This means that multiple threads can work on the same area and commit their changed when they -/// are done. -pub struct FilmTile { - pub bounds: Bound2i, - pub size: Vector2i, - - pixels: Vec<Pixel>, -} - -impl Pixel { - fn new() -> Pixel { - Pixel { - rgb: Default::default(), - samples: 0, - } - } - - fn add(&mut self, c: &Spectrum, weight: Float) { - self.rgb += &(*c * weight); - self.samples += 1; - } - - fn finalize_rgb(&self) -> [u8; 3] { - let spc = (self.rgb / (self.samples as Float)).gamma_correct(); - let (r, g, b) = spc.to_rgb(255.0); - [ - r as u8, - g as u8, - b as u8, - ] - } -} - -impl std::ops::AddAssign<&Self> for Pixel { - fn add_assign(&mut self, op: &Self) { - self.rgb += &op.rgb; - self.samples += op.samples; - } -} - -impl Film { - pub fn new(size: Vector2i) -> Film { - let area = size.x * size.y; - Film { - size, - frame: Bound2i::new(&Vector2i::new(0), &size), - pixels: vec![Pixel::new(); area as usize], - } - } - - /// Creates a new FilmTile from the specified bounds - /// - /// This tile can later be commited with the commit_tile function - pub fn get_tile(&self, bound: &Bound2i) -> FilmTile { - FilmTile::new( - bound, - ) - - } - - /// Commit a tile back on the film - /// - /// This will lock the Film while the changes from the Tile is written - pub fn commit_tile(&mut self, tile: &FilmTile) { - let offset = tile.bounds.min; - - for y in 0 .. tile.size.y { - let rowindex = (offset.y + y) * self.size.x; - let prowindex = y * tile.size.x; - - for x in 0 .. tile.size.x { - let index = offset.x + x + rowindex; - let pindex: i32 = x + prowindex; - - self.pixels[index as usize] += &tile.pixels[pindex as usize]; - } - } - } - - pub fn finalize_image(&self) -> RgbImage { - let mut img = RgbImage::new(self.size.x as u32, self.size.y as u32); - - for y in 0..self.size.y { - let index = y * self.size.x; - for x in 0..self.size.x { - img.put_pixel( - x as u32, - y as u32, - Rgb(self.pixels[(index + x) as usize].finalize_rgb()), - ); - } - } - - img - } -} - -impl FilmTile { - fn new(bounds: &Bound2i) -> FilmTile { - FilmTile { - bounds: bounds.clone(), - pixels: vec![Pixel::new(); bounds.area() as usize], - size: bounds.diagonal(), - } - } - - /// Add a single sample sampled from the scene - pub fn add_sample(&mut self, inp: &Vector2f, c: Spectrum) { - let point = Vector2i::from(inp.floor()); - // Subtract the offset - let point = (point - self.bounds.min).cap(self.size.x-1, self.size.y-1); - - let index = point.x + point.y * self.size.x; - - if let Some(pixel) = self.pixels.get_mut(index as usize) { - pixel.add(&c, 1.0); - } else { - println!("Could not get pixel {} inp: {}, index: {}", point, inp, index); - } - } -} diff --git a/src/camera/filter/mod.rs b/src/camera/filter/mod.rs deleted file mode 100644 index 01d094c..0000000 --- a/src/camera/filter/mod.rs +++ /dev/null @@ -1,32 +0,0 @@ -use crate::vector::Vector2f; -use crate::Float; - -trait Eval { - fn eval(&self, point: &Vector2f) -> Float; -} - -pub struct Filter { - eval: Box<dyn Eval>, - pub radius: Vector2f, -} - -struct BoxFilter {} - -// The same a no filter, and can give aliasing in final image -impl Eval for BoxFilter { - fn eval(&self, _: &Vector2f) -> Float { - 1.0 - } -} - -impl Eval for Filter { - fn eval(&self, point: &Vector2f) -> Float { - self.eval.eval(point) - } -} - -impl Filter { - pub fn new_box(radius: Vector2f) -> Filter { - Filter { radius: radius, eval: Box::new(BoxFilter {}) } - } -} diff --git a/src/camera/mod.rs b/src/camera/mod.rs deleted file mode 100644 index 999b5b5..0000000 --- a/src/camera/mod.rs +++ /dev/null @@ -1,13 +0,0 @@ -//! Implements how light is captured on film and how rays are generated -//! -//! The Film module specifies how calculated spectrum values contribute to the final image. -//! -//! The Camera class generated rays that can be cast into the scene. -//! This requires converting the film coordinates into real coordinates - -pub mod film; -//pub mod filter; -mod camera; - -pub use camera::{Camera, CameraSettings}; -pub use film::Film; diff --git a/src/core/bound2.rs b/src/core/bound2.rs deleted file mode 100644 index bc0e7cd..0000000 --- a/src/core/bound2.rs +++ /dev/null @@ -1,127 +0,0 @@ -//! Implements a 2d axis aligned bounding box -use crate::{Number, Float}; -use super::vector2::Vector2; -use crate::core; -use crate::core::{min, max}; - -/// Implements a region between min and max -#[derive(Clone)] -pub struct Bound2<T: Number> { - pub min: Vector2<T>, - pub max: Vector2<T> -} - -pub type Bound2i = Bound2<i32>; -pub type Bound2f = Bound2<Float>; - -impl<T: Number> Bound2<T> { - /// Creates a new bound from two points - /// - /// p0 does not have to be smaller than p1 - pub fn new(p0: &Vector2<T>, p1: &Vector2<T>) -> Self { - let min = Vector2::new_xy(min(p0.x, p1.x), min(p0.y, p1.y)); - let max = Vector2::new_xy(max(p0.x, p1.x), max(p0.y, p1.y)); - - Self { min, max } - } - - pub fn new_xyxy(x1: T, y1: T, x2: T, y2: T) -> Self { - Self::new( - &Vector2::new_xy(x1, y1), - &Vector2::new_xy(x2, y2), - ) - } - - /// Finds the intersected area between two bounds - pub fn intersect(&self, b: &Bound2<T>) -> Bound2<T> { - Bound2::new( - &Vector2::new_xy(max(self.min.x, b.min.x), max(self.min.y, b.min.y)), - &Vector2::new_xy(min(self.max.x, b.max.x), min(self.max.y, b.max.y)), - ) - } - - - /// Calculates the diagonal vector - /// - /// Can be used to calculate the size of the bound - /// - /// # Examples - /// - /// ``` - /// use rendering::core::Bound2i; - /// let b = Bound2i::new_xyxy(2, 2, 6, 7); - /// let diag = b.diagonal(); - /// - /// assert!(diag.x == 4 && diag.y == 5); - /// ``` - pub fn diagonal(&self) -> Vector2<T> { - self.max - self.min - } - - /// Calculates the area of of the bounded region - /// - /// # Examples - /// - /// ``` - /// use rendering::core::Bound2i; - /// let b = Bound2i::new_xyxy(10, 10, 20, 20); - /// - /// assert!(b.area() == 100); - /// ``` - pub fn area(&self) -> T { - let diag = self.diagonal(); - diag.x * diag.y - } -} - -impl From<&Bound2i> for Bound2f { - fn from(b: &Bound2i) -> Self { - Self { - min: core::Vector2f::from(b.min), - max: core::Vector2f::from(b.max), - } - } -} - -impl From<&Bound2f> for Bound2i { - fn from(b: &Bound2f) -> Self { - Self { - min: core::Vector2i::from(b.min), - max: core::Vector2i::from(b.max), - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - - fn create_test() -> Bound2<i32> { - Bound2::new( - &Vector2::new_xy(1, 2), - &Vector2::new_xy(10, 3) - ) - } - - #[test] - fn area() { - let b = create_test(); - - assert!(b.area() == 9); - } - - #[test] - fn intersect_test() { - let b1 = Bound2i::new_xyxy(10, 10, 20, 20); - let b2 = Bound2i::new_xyxy(2, 11, 22, 17); - - let b = b1.intersect(&b2); - - assert!( - b.min.x == 10 && - b.min.y == 11 && - b.max.x == 20 && - b.max.y == 17 - ) - } -} diff --git a/src/core/bound3.rs b/src/core/bound3.rs deleted file mode 100644 index ce6bb09..0000000 --- a/src/core/bound3.rs +++ /dev/null @@ -1,113 +0,0 @@ -//! Implements 3d axis aligned bounding box -use crate::{Number, Float}; -use super::vector3::{Vector3, Vector3f}; -use crate::core::{min, max}; -use crate::core::Ray; - -#[derive(Clone)] -pub struct Bound3<T: Number> { - pub min: Vector3<T>, - pub max: Vector3<T> -} - -pub type Bound3f = Bound3<Float>; - -impl<T: Number> Bound3<T> { - /// Creates a bound from two points - pub fn new(p0: Vector3<T>, p1: Vector3<T>) -> Self { - // Elliminate some code duplication here - let min = Vector3::new_xyz( - min(p0.x, p1.x), - min(p0.y, p1.y), - min(p0.z, p1.z) - ); - let max = Vector3::new_xyz( - max(p0.x, p1.x), - max(p0.y, p1.y), - max(p0.z, p1.z) - ); - - Self {min, max} - } - - pub fn combine(&self, op: &Self) -> Self { - let min = Vector3::new_xyz( - min(self.min.x, op.min.x), - min(self.min.y, op.min.y), - min(self.min.z, op.min.z) - ); - let max = Vector3::new_xyz( - max(self.max.x, op.max.x), - max(self.max.y, op.max.y), - max(self.max.z, op.max.z) - ); - - Self {min, max} - } - - pub fn and(&self, op: &Self) -> Self { - let min_b = Vector3::new_xyz( - max(self.min.x, op.min.x), - max(self.min.y, op.min.y), - max(self.min.z, op.min.z) - ); - let max_b = Vector3::new_xyz( - min(self.max.x, op.max.x), - min(self.max.y, op.max.y), - min(self.max.z, op.max.z) - ); - - Self {min: min_b, max: max_b} - } - - pub fn area(&self) -> T { - let diag = self.max - self.min; - diag.x * diag.y * diag.z - } -} - -impl Bound3f { - pub const EMPTY: Bound3f = Bound3f{min: Vector3f::ZERO, max: Vector3f::ZERO}; - - /// Calculate whether there is a intersect between a bounding box and a ray - /// - /// # Examples: - /// - /// ``` - /// use rendering::core::{Bound3f, Vector3f, Ray}; - /// use rendering::INFTY; - /// let b = Bound3f::new(Vector3f::new(7.0), Vector3f::new(10.0)); - /// let r1 = Ray::new_to(Vector3f::new(0.0), Vector3f::new(5.0)); - /// let r2 = Ray::new_to(Vector3f::new(-0.0), Vector3f::new(-5.0)); - /// let r3 = Ray::new(Vector3f::new_xyz(-1.0, 0.0, 0.0), Vector3f::new_xyz(1.0, 0.0, 0.0)); - /// - /// assert!(b.intersect(&r1, 0.0, INFTY)); - /// assert!(!b.intersect(&r2, 0.0, INFTY)); - /// assert!(!b.intersect(&r3, 0.0, INFTY)); - /// ``` - pub fn intersect(&self, ray: &Ray, t_min: Float, t_max: Float) -> bool { - println!("BIN: {} -> {}", self.min, self.max); - // Method stolen from Ray tracing the next week. - // They mention its from pixar - for i in 0..3 { - let inv = 1.0 / ray.direction[i]; - let mut t0 = (self.min[i] - ray.origin[i]) * inv; - let mut t1 = (self.max[i] - ray.origin[i]) * inv; - - if inv < 0.0 { - let tmp = t0; - t0 = t1; - t1 = tmp; - } - - let t_min = max(t0, t_min); - let t_max = min(t1, t_max); - - if t_max <= t_min { - return false; - } - } - - return true; - } -} diff --git a/src/core/mod.rs b/src/core/mod.rs deleted file mode 100644 index 95d450c..0000000 --- a/src/core/mod.rs +++ /dev/null @@ -1,34 +0,0 @@ -//! Contains a collection of core modules used by other modules -//! -//! Also creates a shortcut for some common types - -mod vector2; -mod vector3; -mod bound2; -mod bound3; -mod spectrum; -mod ray; - -pub use vector2::{Vector2i, Vector2f}; -pub use vector3::Vector3f; -pub use bound2::{Bound2i, Bound2f}; -pub use bound3::Bound3f; -pub use spectrum::Spectrum; -pub use ray::Ray; - -use crate::Number; - -pub fn min<T: Number> (a: T, b: T) -> T { - if b < a { - return b; - } - a -} - -pub fn max<T: Number> (a: T, b: T) -> T { - if b > a { - return b; - } - a -} - diff --git a/src/core/ray.rs b/src/core/ray.rs deleted file mode 100644 index 19d3cf1..0000000 --- a/src/core/ray.rs +++ /dev/null @@ -1,35 +0,0 @@ -//! The ray class used when probing the 3d scene -use crate::core::Vector3f; -use crate::Float; - -/// A ray that is sent into the world. -/// This is the main type used for testing intersections. -pub struct Ray { - /// Origin of the ray - pub origin: Vector3f, - /// Direction is assumed to be a unit vector. - pub direction: Vector3f, -} - -impl Ray { - pub fn new(origin: Vector3f, direction: Vector3f) -> Ray { - Ray { - origin, - direction, - } - } - - pub fn new_to(origin: Vector3f, target: Vector3f) -> Ray { - let dir = (target - origin).norm(); - Ray { - origin, - direction: dir - } - } - - /// Resolve a point on the ray at time t - pub fn at(&self, t: Float) -> Vector3f { - self.origin + self.direction * t - } -} - diff --git a/src/core/spectrum.rs b/src/core/spectrum.rs deleted file mode 100644 index ed3505b..0000000 --- a/src/core/spectrum.rs +++ /dev/null @@ -1,88 +0,0 @@ -//! Used to represent color -//! -//! Currently only implements RGB colors -use crate::Float; - -// TODO implement SampledSpectrum instead for nicer images - -#[derive(Clone, Copy, Default)] -pub struct Spectrum { - c: [Float; 3], -} - -impl Spectrum { - pub const ZERO: Self = Spectrum { c: [0.0; 3] }; - pub const WHITE: Self = Spectrum { c: [1.0; 3] }; - - pub fn new_rgb(r: Float, g: Float, b: Float) -> Spectrum { - Spectrum { c: [r, g, b] } - } - - pub fn to_rgb(&self, scale: Float) -> (Float, Float, Float) { - (self.c[0] * scale, self.c[1] * scale, self.c[2] * scale) - } - - pub fn gamma_correct(&self) -> Self { - Self::new_rgb( - self.c[0].sqrt(), - self.c[1].sqrt(), - self.c[2].sqrt(), - ) - } -} - -impl std::ops::Mul<Float> for Spectrum { - type Output = Spectrum; - - fn mul(self, op: Float) -> Self::Output { - Self::Output::new_rgb( - self.c[0] * op, - self.c[1] * op, - self.c[2] * op, - ) - } -} - -impl std::ops::Mul for Spectrum { - type Output = Spectrum; - - fn mul(self, op: Self) -> Self::Output { - Self::Output::new_rgb( - self.c[0] * op.c[0], - self.c[1] * op.c[1], - self.c[2] * op.c[2], - ) - } -} - -impl std::ops::Div<Float> for Spectrum { - type Output = Spectrum; - - fn div(self, op: Float) -> Self::Output { - Self::Output::new_rgb( - self.c[0] / op, - self.c[1] / op, - self.c[2] / op, - ) - } -} - -impl std::ops::Add for Spectrum { - type Output = Spectrum; - - fn add(self, op: Self) -> Self::Output { - Self::Output::new_rgb( - self.c[0] + op.c[0], - self.c[1] + op.c[1], - self.c[2] + op.c[2], - ) - } -} - -impl std::ops::AddAssign<&Self> for Spectrum { - fn add_assign(&mut self, op: &Self) { - self.c[0] += op.c[0]; - self.c[1] += op.c[1]; - self.c[2] += op.c[2]; - } -} diff --git a/src/core/vector2.rs b/src/core/vector2.rs deleted file mode 100644 index 405b12a..0000000 --- a/src/core/vector2.rs +++ /dev/null @@ -1,167 +0,0 @@ -//! Implements 2d vectors -//! -//! This is implemented generictly with types that fit in the Number trait -use crate::{Float, Number}; -use std::ops::{Sub, Add, Mul, Div}; -use std::fmt; -use std::cmp::min; - -#[derive(Clone, Copy)] -pub struct Vector2<T: Number> { - pub x: T, - pub y: T, -} - -pub type Vector2f = Vector2<Float>; -pub type Vector2i = Vector2<i32>; - - -impl<T: Number> Vector2<T> { - pub fn new(initial: T) -> Vector2<T> { - Vector2 { x: initial, y: initial } - } - - pub fn new_xy(x: T, y: T) -> Vector2<T> { - Vector2 { x, y } - } -} - -impl<T: Number> Sub for Vector2<T> { - type Output = Self; - fn sub(self, op: Self) -> Self::Output { - Self::new_xy( - self.x - op.x, - self.y - op.y, - ) - } -} - -impl<T: Number> Add for Vector2<T> { - type Output = Self; - fn add(self, op: Self) -> Self::Output { - Self::new_xy( - self.x + op.x, - self.y + op.y, - ) - } -} - -impl<T: Number> Mul for Vector2<T> { - type Output = Self; - fn mul(self, op: Self) -> Self::Output { - Self::new_xy( - self.x * op.x, - self.y * op.y, - ) - } -} - -impl<T: Number> Mul<T> for Vector2<T> { - type Output = Self; - fn mul(self, op: T) -> Self::Output { - Self::new_xy( - self.x * op, - self.y * op, - ) - } -} - -impl<T: Number> Div for Vector2<T> { - type Output = Self; - fn div(self, op: Self) -> Self::Output { - Self::new_xy( - self.x / op.x, - self.y / op.y, - ) - } -} - -impl<T: Number> fmt::Display for Vector2<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_fmt(format_args!("[{}, {}]", self.x, self.y)) - } -} - -impl From<Vector2i> for Vector2f { - fn from(v: Vector2i) -> Self { - Self { - x: v.x as Float, - y: v.y as Float, - } - } -} - -impl From<Vector2f> for Vector2i { - fn from(v: Vector2f) -> Self { - Self { - x: v.x as i32, - y: v.y as i32, - } - } -} - -impl Vector2i { - pub const ZERO: Self = Vector2i {x: 0, y: 0}; - - pub fn cap(&self, x: i32, y: i32) -> Self { - Self::new_xy( - min(self.x, x), - min(self.y, y), - ) - } -} - -impl Vector2f { - pub fn length(&self) -> Float { - (self.x*self.x + self.y*self.y).sqrt() - } - - pub fn ceil(&self) -> Self { - Self::new_xy( - self.x.ceil(), - self.y.ceil() - ) - } - - pub fn floor(&self) -> Self { - Self::new_xy( - self.x.floor(), - self.y.floor() - ) - } -} - - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn new_vec2() { - let v = Vector2::new_xy(2.0, 10.0); - - assert!(v.x == 2.0 && v.y == 10.0); - - let v = Vector2::new(3); - - assert!(v.x == 3 && v.y == 3); - } - - #[test] - fn sub_vec2() { - let v1 = Vector2::new_xy(10, 11); - let v2 = Vector2::new_xy(2, 3); - - let v3 = v1-v2; - assert!(v3.x == 8 && v3.y == 8); - } - - #[test] - fn add_vec2() { - let v1 = Vector2::new_xy(10, 11); - let v2 = Vector2::new_xy(2, 3); - - let v3 = v1+v2; - assert!(v3.x == 12 && v3.y == 14); - } -} diff --git a/src/core/vector3.rs b/src/core/vector3.rs deleted file mode 100644 index 1cc6f60..0000000 --- a/src/core/vector3.rs +++ /dev/null @@ -1,185 +0,0 @@ -//! Implements 3d vectors -//! -//! Also add more 3d math things needed for shading and 3d calculations. -use crate::{Float, Number, NEAR_ZERO}; -use std::ops::{Mul, Sub, Add, DivAssign, Neg, AddAssign, Index}; -use std::fmt; - -#[derive(Clone, Copy)] -pub struct Vector3<T: Number> { - pub x: T, - pub y: T, - pub z: T, -} - -pub type Vector3f = Vector3<Float>; - -impl<T: Number> Vector3<T> { - pub fn new(initial: T) -> Vector3<T> { - Vector3 { - x: initial, - y: initial, - z: initial, - } - } - - pub fn new_xyz(x: T, y: T, z: T) -> Vector3<T> { - Vector3 { x, y, z} - } -} - -impl<T: Number> Sub for Vector3<T> { - type Output = Self; - fn sub(self, op: Self) -> Self::Output { - Self::new_xyz( - self.x - op.x, - self.y - op.y, - self.z - op.z, - ) - } -} - -impl<T: Number> Add for Vector3<T> { - type Output = Self; - fn add(self, op: Self) -> Self::Output { - Self::new_xyz( - self.x + op.x, - self.y + op.y, - self.z + op.z, - ) - } -} - -impl<T: Number> Add<T> for Vector3<T> { - type Output = Self; - - fn add(self, op: T) -> Self::Output { - Self::new_xyz( - self.x + op, - self.y + op, - self.z + op, - ) - } -} - -impl<T: Number> Mul<T> for Vector3<T> { - type Output = Self; - fn mul(self, op: T) -> Self::Output { - Self::Output::new_xyz( - self.x * op, - self.y * op, - self.z * op, - ) - } -} - -impl<T: Number> Neg for Vector3<T> { - type Output = Self; - - fn neg(self) -> Self::Output { - Self::Output::new_xyz( - -self.x, - -self.y, - -self.z, - ) - } -} - -impl<T: Number> AddAssign<&Self> for Vector3<T> { - fn add_assign(&mut self, op: &Self) { - self.x += op.x; - self.y += op.y; - self.z += op.z; - } -} - -impl<T: Number> DivAssign<T> for Vector3<T> { - fn div_assign(&mut self, op: T) { - self.x /= op; - self.y /= op; - self.z /= op; - } -} - -impl<T: Number> fmt::Display for Vector3<T> { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - f.write_fmt(format_args!("[{}, {}, {}]", self.x, self.y, self.z)) - } -} - -// Ohh god -impl<T: Number> Index<u32> for Vector3<T> { - type Output = T; - - fn index(&self, i: u32) -> &Self::Output { - match i { - 0 => &self.x, - 1 => &self.y, - 2 => &self.z, - _ => panic!("index out of bounds: index {} is not possible with 3d vector", i) - } - } -} - -impl Vector3f { - pub const ZERO: Self = Vector3f {x: 0.0, y: 0.0, z: 0.0}; - - /// Calculates the length times itself - /// - /// This is faster than using len * len as the square is ommited - pub fn len_squared(&self) -> Float { - self.x * self.x + self.y * self.y + self.z * self.z - } - - pub fn length(&self) -> Float { - self.len_squared().sqrt() - } - - pub fn dot(&self, op: &Self) -> Float { - self.x * op.x + self.y * op.y + self.z * op.z - } - - /// Inplace normal instead of creating a new vector - /// - /// # Example - /// - /// ``` - /// use rendering::core::Vector3f; - /// let mut v = Vector3f::new_xyz(10.0, 0.0, 0.0); - /// v.norm_in(); - /// assert!(v.x == 1.0); - /// ``` - pub fn norm_in(&mut self) { - // TODO Experiment with checking for normality with len_squared - let len = self.length(); - if len == 0.0 { - *self = Self::new(0.0); - } - - *self /= len; - } - - pub fn norm(&self) -> Self { - let mut new = *self; - new.norm_in(); - new - } - - pub fn cross(&self, op: &Self) -> Self { - Self::new_xyz( - self.y * op.z - self.z * op.y, - self.z * op.x - self.x * op.z, - self.x * op.y - self.y * op.x, - ) - - } - - /// Check if vector is close to [0, 0, 0] - /// - /// This is based on the NEAR_ZERO constant - pub fn near_zero(&self) -> bool { - (self.x.abs() < NEAR_ZERO) && - (self.y.abs() < NEAR_ZERO) && - (self.z.abs() < NEAR_ZERO) - } -} diff --git a/src/lib.rs b/src/lib.rs deleted file mode 100644 index 0e6674d..0000000 --- a/src/lib.rs +++ /dev/null @@ -1,41 +0,0 @@ -pub mod core; -pub mod camera; -pub mod render; -pub mod world; -pub mod trace; -pub mod sample; -pub mod material; - -use std::ops::{Add, Sub, Mul, DivAssign, AddAssign, Neg, Div}; -use std::cmp; -use std::fmt; -use std::f64::consts::PI; - -/// Trait used to implement generics -/// -/// This is used in Bound and Vectors -pub trait Number: - Copy + - cmp::PartialOrd + - Sub<Output = Self> + - Add<Output = Self> + - Mul<Output = Self> + - Neg<Output = Self> + - Div<Output = Self> + - DivAssign + - AddAssign + - fmt::Display -{} - -impl Number for i32 {} -impl Number for f32 {} -impl Number for f64 {} - -/// Used for representing floating point values throughout the program -/// -/// A higher precision type will require more ram -pub type Float = f64; - -pub const M_PI: Float = PI; -pub const NEAR_ZERO: Float = 1e-8; -pub const INFTY: Float = f64::INFINITY; diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index dd67fdc..0000000 --- a/src/main.rs +++ /dev/null @@ -1,63 +0,0 @@ -use rendering::camera::{Camera, Film, CameraSettings}; -use rendering::world::{Scene, Object, shapes::Sphere}; -use rendering::trace::DefaultTracer; -use rendering::core::{Vector2i, Vector3f, Spectrum}; -use rendering::render::{RenderContext, RenderCoord}; -use rendering::sample::UniformSampler; -use rendering::material::*; - -use std::sync::Arc; - -fn main() { - let res = Vector2i::new_xy(500, 500); - - let cam = Camera::new(&CameraSettings { - target: Vector3f::new_xyz(0.0, 0.0, -1.0), - origin: Vector3f::new_xyz(0.0, 0.0, 1.0), - up: Vector3f::new_xyz(0.0, 1.0, 0.0), - fov: 45.0, - filmsize: res, - focus: None, - aperture: Some(10.0), - //aperture: None, - }); - - let brown = Arc::new(Lambertian::new(Spectrum::new_rgb(0.5, 0.3, 0.0))); - let blue = Arc::new(Lambertian::new(Spectrum::new_rgb(0.0, 0.3, 0.7))); - let green = Arc::new(Lambertian::new(Spectrum::new_rgb(0.0, 0.7, 0.3))); - let metal = Arc::new(Reflectant::new(Spectrum::new_rgb(0.8, 0.8, 0.9), Some(1.0))); - let glass = Arc::new(Dielectric::new(1.5)); - let sun = Arc::new(DiffuseLight::new_white(50.0)); - - let mut scn = Scene::new(); - scn.add_objects(vec![ - Object::new(glass, Sphere::new(0.2, Vector3f::new_xyz(0.0, 0.0, -1.0))), - Object::new(blue, Sphere::new(0.5, Vector3f::new_xyz(1.0, 0.0, -1.5))), - Object::new(green, Sphere::new(0.3, Vector3f::new_xyz(0.5, 0.0, -2.5))), - Object::new(brown, Sphere::new(100.0, Vector3f::new_xyz(0.0, -100.5, -1.0))), - Object::new(metal, Sphere::new(0.2, Vector3f::new_xyz(-0.5, 0.0, -1.0))), - Object::new(sun, Sphere::new(0.4, Vector3f::new_xyz(-1.0, 3.0, 0.0))), - ]); - - let tracer = DefaultTracer::new(&scn, Some(50), - //Some(Box::new(SkyLight::new())) - None - ); - - let mut sampler = UniformSampler::new(); - - let ctx = RenderContext { cam: &cam, trc: &tracer }; - - let mut film = Film::new(res); - { - let coord = RenderCoord::new(&mut film, Vector2i::new_xy(50, 50), 16000); - - coord.run_threaded(&ctx, &mut sampler, 8); - } - - let image = film.finalize_image(); - if let Err(e) = image.save("test.png") { - println!("Failed to save {}", e); - } - -} diff --git a/src/material/dielectric.rs b/src/material/dielectric.rs deleted file mode 100644 index c8dc279..0000000 --- a/src/material/dielectric.rs +++ /dev/null @@ -1,56 +0,0 @@ -use super::Material; -use crate::core::{min, Vector3f, Spectrum, Ray}; -use crate::world::Intersection; -use crate::sample::Sampler; -use crate::Float; -use crate::material::reflectant::reflect; - -pub struct Dielectric { - ratio: Float, -} - -// Implementation from RTIOW -fn refract(v: Vector3f, n: Vector3f, r_ratio: Float, cos_theta: Float) -> Vector3f { - let r_perp = (v + n * cos_theta) * r_ratio; - let r_parallel = n * (-(1.0 - r_perp.len_squared()).abs().sqrt()); - - r_perp + r_parallel -} - -// Schlick Approximation, explained in RTIOW -fn fresnel(cos: Float, ratio: Float) -> Float { - let mut r0 = (1.0-ratio) / (1.0+ratio); - r0 = r0 * r0; - - r0 + (1.0-r0)*(1.0-cos).powi(5) -} - -impl Dielectric { - pub fn new(ratio: Float) -> Self { - Self { ratio } - } -} - -impl Material for Dielectric { - // Implementation from RTIOW - fn scatter(&self, ray: &Ray, i: &Intersection, sampler: &mut dyn Sampler) -> Option<(Spectrum, Ray)> { - let ratio = if i.front {1.0/self.ratio} else {self.ratio}; - - let ray_dir = ray.direction.norm(); - let cos_theta = min((-ray_dir).dot(&i.n), 1.0); - let sin_theta = (1.0 - cos_theta*cos_theta).sqrt(); - - // Test if it is possible for the ray the retract or if it must reflect. - let cannot_refract = (ratio * sin_theta) > 1.0; - let direction = if cannot_refract || (fresnel(cos_theta, ratio) > sampler.get_sample()) { - reflect(ray_dir, i.n) - } else { - refract(ray_dir, i.n, ratio, cos_theta) - }; - - Some(( - Spectrum::WHITE, - Ray::new(i.p, direction), - )) - } -} diff --git a/src/material/diffuse_light.rs b/src/material/diffuse_light.rs deleted file mode 100644 index fe462a8..0000000 --- a/src/material/diffuse_light.rs +++ /dev/null @@ -1,27 +0,0 @@ -use super::Material; -use crate::core::{Ray, Spectrum}; -use crate::Float; - -pub struct DiffuseLight { - color: Spectrum, -} - -impl DiffuseLight { - pub fn new(c: Spectrum) -> Self { - Self { - color: c, - } - } - - pub fn new_white(s: Float) -> Self { - Self { - color: Spectrum::new_rgb(s, s, s), - } - } -} - -impl Material for DiffuseLight { - fn emitted(&self, _: &Ray) -> Option<Spectrum> { - Some(self.color) - } -} diff --git a/src/material/lambertian.rs b/src/material/lambertian.rs deleted file mode 100644 index 3df6522..0000000 --- a/src/material/lambertian.rs +++ /dev/null @@ -1,38 +0,0 @@ -use super::Material; -use crate::core::{Ray, Spectrum}; -use crate::world::Intersection; -use crate::sample::Sampler; - -use std::rc::Rc; - -pub struct Lambertian { - color: Spectrum, -} - -impl Lambertian { - pub fn new(c: Spectrum) -> Lambertian { - Lambertian { - color: c, - } - } - - pub fn new_rc(c: Spectrum) -> Rc<dyn Material> { - Rc::new(Self::new(c)) - } -} - -impl Material for Lambertian { - fn scatter(&self, _: &Ray, i: &Intersection, sampler: &mut dyn Sampler) -> Option<(Spectrum, Ray)> { - let mut newray = Ray { - origin: i.p, - direction: i.n + sampler.get_unit_vector(), - }; - - // Make sure that the resulting direction is not (0, 0, 0) - if newray.direction.near_zero() { - newray.direction = i.n; - } - - Some((self.color, newray)) - } -} diff --git a/src/material/mod.rs b/src/material/mod.rs deleted file mode 100644 index 6732598..0000000 --- a/src/material/mod.rs +++ /dev/null @@ -1,25 +0,0 @@ -use crate::core::{Ray, Spectrum}; -use crate::world::Intersection; -use crate::sample::Sampler; - -mod lambertian; -mod diffuse_light; -mod sky_light; -mod dielectric; -pub mod reflectant; - -pub use lambertian::Lambertian; -pub use diffuse_light::DiffuseLight; -pub use sky_light::SkyLight; -pub use dielectric::Dielectric; -pub use reflectant::Reflectant; - -pub trait Material: Sync + Send { - fn scatter(&self, _: &Ray, _: &Intersection, _: &mut dyn Sampler) -> Option<(Spectrum, Ray)> { - None - } - - fn emitted(&self, _: &Ray) -> Option<Spectrum> { - None - } -} diff --git a/src/material/reflectant.rs b/src/material/reflectant.rs deleted file mode 100644 index b5ec0ae..0000000 --- a/src/material/reflectant.rs +++ /dev/null @@ -1,40 +0,0 @@ -use crate::Float; -use crate::core::{Ray, Spectrum, Vector3f}; -use crate::world::Intersection; -use super::Material; -use crate::sample::Sampler; - -pub struct Reflectant { - color: Spectrum, - fuzz: Option<Float>, -} - -impl Reflectant { - pub fn new(c: Spectrum, fuzz: Option<Float>) -> Reflectant { - Reflectant { - color: c, - fuzz, - } - } -} - -pub fn reflect(v: Vector3f, n: Vector3f) -> Vector3f { - v - n * (2.0 * v.dot(&n)) -} - -impl Material for Reflectant { - fn scatter(&self, ray: &Ray, i: &Intersection, sampler: &mut dyn Sampler) -> Option<(Spectrum, Ray)> { - // Find reflectance vector - let mut reflected = reflect(ray.direction, i.n); - if let Some(fuzz) = self.fuzz { - reflected += &(sampler.get_unit_vector() * fuzz); - } - - Some(( - self.color, - Ray::new(i.p, reflected.norm()), - )) - } -} - - diff --git a/src/material/sky_light.rs b/src/material/sky_light.rs deleted file mode 100644 index e499b6e..0000000 --- a/src/material/sky_light.rs +++ /dev/null @@ -1,19 +0,0 @@ -use super::Material; -use crate::core::{Ray, Spectrum}; - -pub struct SkyLight { -} - -impl SkyLight { - pub fn new() -> Self { - Self {} - } -} - -impl Material for SkyLight { - fn emitted(&self, ray: &Ray) -> Option<Spectrum> { - let t = (ray.direction.norm().y + 1.0) * 0.5; - - Some(Spectrum::new_rgb(1.0, 1.0, 1.0) * (1.0-t) + Spectrum::new_rgb(0.5, 0.7, 1.0) * t) - } -} diff --git a/src/render/coordinator.rs b/src/render/coordinator.rs deleted file mode 100644 index e2f825b..0000000 --- a/src/render/coordinator.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! Split a image into small tiles -//! -//! This enables multithreaded rendering. - -use super::{RenderTask, RenderContext}; -use crate::camera::Film; -use crate::core::{Bound2i, Vector2i}; -use crate::sample::Sampler; - -use std::sync::{Mutex}; - -struct Tiler { - tilesize: Vector2i, - - fullbound: Bound2i, - tilemap_size: Vector2i, - tilemap_area: i32, - - next_tile: i32, - tiles_done: i32, -} - -pub struct RenderCoord<'a> { - film: Mutex<&'a mut Film>, - samples: u32, - tiler: Mutex<Tiler>, -} - -impl Tiler { - pub fn new(fullsize: Vector2i, tilesize: Vector2i) -> Tiler { - assert!(tilesize.x != 0 && tilesize.y != 0); - - let tilemap_size = fullsize / tilesize; - - Self { - tilesize, - - fullbound: Bound2i::new(&Vector2i::ZERO, &fullsize), - tilemap_size, - tilemap_area: (tilemap_size.x * tilemap_size.y), - - next_tile: 0, - tiles_done: 0, - } - - } - - pub fn next_tile(&mut self) -> Option<(Bound2i, i32)> { - // Check if we are outside - if self.next_tile >= self.tilemap_area { - return None; - } - - // Convert the tile to xy in tilemap - let tile = Vector2i::new_xy(self.next_tile / self.tilemap_size.x, self.next_tile % self.tilemap_size.x); - let tile_index = self.next_tile; - - self.next_tile += 1; - - // Create a bound from the tilecoordinate - let tile_start = tile * self.tilesize; - // We need to make sure the resulting tile is inside the image bound - Some(( - Bound2i::new(&tile_start, &(tile_start + self.tilesize)).intersect(&self.fullbound), - tile_index, - )) - } - - /// Mark a index as done and return the overall process - pub fn mark_done(&mut self, _index: i32) -> f32 { - self.tiles_done += 1; - - println!("Progress: {}/{}", self.tiles_done, self.tilemap_area); - self.tiles_done as f32 / self.tilemap_area as f32 - } -} - -impl<'a> RenderCoord<'a> { - pub fn new(film: &'a mut Film, tilesize: Vector2i, samples: u32) -> Self { - let size = film.size; - - Self { - film: Mutex::new(film), - samples, - tiler: Mutex::new(Tiler::new(size, tilesize)), - } - } - - pub fn next_task(&self) -> Option<RenderTask> { - let (tile_bound, index) = self.tiler.lock().unwrap().next_tile()?; - - let film_tile = { - let film = self.film.lock().unwrap(); - Box::new(film.get_tile(&tile_bound)) - }; - - Some(RenderTask::new(film_tile, self.samples, index)) - } - - pub fn finish_task(&self, task: &RenderTask) { - { - let mut film = self.film.lock().unwrap(); - film.commit_tile(task.tile.as_ref()); - } - - self.tiler.lock().unwrap().mark_done(task.tile_index); - } - - pub fn work(&self, ctx: &RenderContext, sampler: &mut dyn Sampler) { - while let Some(mut task) = self.next_task() { - task.render(ctx, sampler); - self.finish_task(&task); - } - } - - pub fn run_threaded(&self, ctx: &RenderContext, sampler: &mut (dyn Sampler + Send), threads: u32) { - crossbeam::scope(|scope| { - for _ in 0..threads { - let mut sampler = sampler.clone_and_seed(); - scope.spawn(move |_| { - self.work(ctx, sampler.as_mut()); - }); - } - }).unwrap(); - } -} diff --git a/src/render/mod.rs b/src/render/mod.rs deleted file mode 100644 index b81aaf5..0000000 --- a/src/render/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod task; -mod coordinator; - -pub use task::{RenderContext, RenderTask}; -pub use coordinator::RenderCoord; diff --git a/src/render/task.rs b/src/render/task.rs deleted file mode 100644 index 017fe24..0000000 --- a/src/render/task.rs +++ /dev/null @@ -1,49 +0,0 @@ -//! Implements the main render loop -//! -//! This is not a final design -use crate::camera::film::FilmTile; -use crate::camera::Camera; -use crate::trace::{DefaultTracer, Tracer}; -use crate::sample::Sampler; - -use crate::core::{Vector2f}; -use crate::Float; - -pub struct RenderTask { - pub tile: Box<FilmTile>, - samples: u32, - pub tile_index: i32, -} - -pub struct RenderContext<'a> { - pub cam: &'a Camera, - pub trc: &'a DefaultTracer<'a>, -} - -impl RenderTask { - pub fn new(tile: Box<FilmTile>, samples: u32, tile_index: i32) -> Self { - Self { tile, samples, tile_index } - } - - fn render_at(&mut self, ctx: &RenderContext, x: i32, y: i32, sampler: &mut dyn Sampler) { - let corner = Vector2f::new_xy(x as Float, y as Float); - - for _ in 0..self.samples { - let p = corner + sampler.get_sample_2d(); - - // Create a ray - let (r, _) = ctx.cam.generate_ray(&p, sampler); - - self.tile.add_sample(&p, ctx.trc.trace(sampler, &r)); - } - } - - pub fn render(&mut self, ctx: &RenderContext, sampler: &mut dyn Sampler) { - let b = self.tile.bounds.clone(); - for y in b.min.y .. b.max.y { - for x in b.min.x .. b.max.x { - self.render_at(ctx, x, y, sampler); - } - } - } -} diff --git a/src/sample/mod.rs b/src/sample/mod.rs deleted file mode 100644 index 1a53921..0000000 --- a/src/sample/mod.rs +++ /dev/null @@ -1,46 +0,0 @@ -use crate::{M_PI, Float}; -use crate::core::{Vector3f, Vector2f}; - -mod uniform; - -pub use uniform::UniformSampler; - -fn distribute_between(x: Float, a: Float, b: Float) -> Float { - x * (b - a) + a -} - -pub trait Sampler { - fn get_sample(&mut self) -> Float; - - fn get_sample_2d(&mut self) -> Vector2f { - Vector2f::new_xy(self.get_sample(), self.get_sample()) - } - - fn clone_and_seed(&mut self) -> Box<dyn Sampler + Send>; - - fn get_unit_vector(&mut self) -> Vector3f { - let s2d = self.get_sample_2d(); - - let lambda = distribute_between(s2d.x, -M_PI, M_PI); - let costheta = 2.0 * s2d.y - 1.0; - let sintheta = costheta.acos().sin(); - - Vector3f::new_xyz( - lambda.cos() * sintheta, - lambda.sin() * sintheta, - costheta, - ) - } - - fn get_in_circle(&mut self) -> Vector2f { - let s2d = self.get_sample_2d(); - - let d = s2d.x.sqrt(); - let theta = s2d.y * 2.0 * M_PI; - - Vector2f::new_xy( - d * theta.cos(), - d * theta.sin(), - ) - } -} diff --git a/src/sample/uniform.rs b/src/sample/uniform.rs deleted file mode 100644 index c144f27..0000000 --- a/src/sample/uniform.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::Float; -use super::Sampler; - -use rand::prelude::*; -use rand::distributions::Uniform; -use rand_pcg::Pcg32; - -#[derive(Clone)] -pub struct UniformSampler { - r: Pcg32, - d: Uniform<Float>, -} - -impl UniformSampler { - pub fn new() -> Self { - Self::default() - } -} - -impl Default for UniformSampler { - fn default() -> Self { - Self { - r: Pcg32::seed_from_u64(1), - d: Uniform::new(0.0, 1.0), - } - } -} - -impl Sampler for UniformSampler { - fn get_sample(&mut self) -> Float { - self.d.sample(&mut self.r) - } - - fn clone_and_seed(&mut self) -> Box<dyn Sampler + Send> { - let mut n = self.clone(); - n.r = Pcg32::seed_from_u64(self.r.next_u64()); - Box::new(n) - } -} diff --git a/src/trace/mod.rs b/src/trace/mod.rs deleted file mode 100644 index d45fb23..0000000 --- a/src/trace/mod.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::world::{Hittable, Scene}; -use crate::core::{Spectrum, Ray, Vector3f}; -use crate::sample::Sampler; - -mod pathtrace; -pub use pathtrace::PathTracer; - -/// Simple surface normal tracer -/// -/// This ray tracer bases color values on the hit surface normals -pub struct NormTracer<'a> { - scn: &'a Scene, -} - -/// Alias for chosen trace implementation. -/// -/// This is swiched at compile time to save alot of time. -pub type DefaultTracer<'a> = PathTracer<'a>; - -pub trait Tracer { - fn trace(&self, sampler: &mut dyn Sampler, ray: &Ray) -> Spectrum; -} - -impl NormTracer<'_> { - pub fn new(scn: &Scene) -> NormTracer { - NormTracer {scn} - } -} - -impl Tracer for NormTracer<'_> { - fn trace(&self, _: &mut dyn Sampler, ray: &Ray) -> Spectrum { - // Trace ray, we dont care about material - if let Some(i) = self.scn.intersect(ray) { - let norm = i.n * 0.5 + Vector3f::new(0.5); - - return Spectrum::new_rgb(norm.x, norm.y, norm.z); - } - - Spectrum::new_rgb(0.0, 0.0, 0.0) - } -} diff --git a/src/trace/pathtrace.rs b/src/trace/pathtrace.rs deleted file mode 100644 index f53e433..0000000 --- a/src/trace/pathtrace.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::world::{Hittable, Scene}; -use crate::core::{Ray, Spectrum}; -use crate::material::Material; -use crate::sample::Sampler; -use super::Tracer; - -pub struct PathTracer<'a> { - depth: i32, - scn: &'a Scene, - background: Option<Box<dyn Material>>, -} - -impl PathTracer<'_> { - pub fn new(scn: &Scene, depth: Option<i32>, background: Option<Box<dyn Material>>) -> PathTracer { - let depth = depth.unwrap_or(-1); - - PathTracer { - depth, - scn, - background, - } - } - - pub fn trace_recur(&self, sampler: &mut dyn Sampler, ray: &Ray, depth: i32) -> Spectrum { - - if depth == 0 { - return Spectrum::ZERO; - } - - if let Some(i) = self.scn.intersect(ray) { - // Extract material or default - if let Some(mat) = i.m { - let mut col = Spectrum::ZERO; - - if let Some((scalar, nray)) = mat.scatter(ray, &i, sampler) { - col += &(self.trace_recur(sampler, &nray, depth-1) * scalar); - } - - if let Some(c) = mat.emitted(ray) { - col += &c; - } - - return col; - } - } - - // If no color return background - if let Some(back) = &self.background { - back.emitted(ray).unwrap_or(Spectrum::ZERO) - } else { - Spectrum::ZERO - } - } -} - -impl Tracer for PathTracer<'_> { - fn trace(&self, sampler: &mut dyn Sampler, ray: &Ray) -> Spectrum { - self.trace_recur(sampler, ray, self.depth) - } -} diff --git a/src/world/container/list.rs b/src/world/container/list.rs deleted file mode 100644 index 22b6d88..0000000 --- a/src/world/container/list.rs +++ /dev/null @@ -1,56 +0,0 @@ -use crate::world::{Object, Hittable, Intersection}; -use crate::core::{Bound3f, Ray}; - - -pub struct HittableList { - elems: Vec<Object>, -} - -impl HittableList { - pub fn new() -> Self { - Self::default() - } - - pub fn add(&mut self, h: Object) { - self.elems.push(h); - } -} - -impl Hittable for HittableList { - fn intersect(&self, ray: &Ray) -> Option<Intersection> { - let mut min: Option<Intersection> = None; - - for e in self.elems.iter() { - if let Some(i) = e.intersect(&ray) { - match min { - // Do nothing if distance is bigger than min - Some(ref min_i) if min_i.t < i.t => {}, - // If no existing min or closer than - _ => min = Some(i), - } - } - } - - min - } - - fn bounding_box(&self) -> Bound3f { - let mut bound: Bound3f = Bound3f::EMPTY; - - for e in self.elems.iter() { - let eb = e.bounding_box(); - bound = bound.combine(&eb); - } - - bound - } -} - -impl Default for HittableList { - fn default() -> Self { - Self { - elems: Vec::new(), - } - } -} - diff --git a/src/world/container/mod.rs b/src/world/container/mod.rs deleted file mode 100644 index 35d4693..0000000 --- a/src/world/container/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod list; - -pub use list::HittableList; diff --git a/src/world/hittable.rs b/src/world/hittable.rs deleted file mode 100644 index e11a3bc..0000000 --- a/src/world/hittable.rs +++ /dev/null @@ -1,41 +0,0 @@ -use crate::core::{Vector3f, Bound3f, Ray}; -use crate::Float; -use crate::material::Material; - -/// Returns the context of a intersection -pub struct Intersection<'a> { - /// Normal vector at intersection - pub n: Vector3f, - pub p: Vector3f, - pub front: bool, - pub t: Float, - pub m: Option<&'a dyn Material>, -} - -impl<'a> Intersection<'a> { - pub fn new(out_normal: Vector3f, point: Vector3f, ray: &Ray, t: Float) -> Self { - let front = ray.direction.dot(&out_normal) < 0.0; - Intersection { - n: { if front { out_normal } else { -out_normal } }, - front, - p: point, - m: None, - t, - } - } - - pub fn add_material_if_none(&mut self, mat: &'a dyn Material) { - if let None = self.m { - self.m = Some(mat); - } - } -} - -/// Defines a common trait for objects in the scene -pub trait Hittable: Sync + Send { - /// Returns the intersection with ray - fn intersect(&self, ray: &Ray) -> Option<Intersection>; - - /// Returns the axis alligned bounding box containing self - fn bounding_box(&self) -> Bound3f; -} diff --git a/src/world/mod.rs b/src/world/mod.rs deleted file mode 100644 index cba6ddc..0000000 --- a/src/world/mod.rs +++ /dev/null @@ -1,42 +0,0 @@ -//! Manages world objects, and implements intersection -pub mod shapes; - -mod scene; -pub mod container; -mod hittable; -pub use scene::*; -pub use hittable::{Intersection, Hittable}; -pub use shapes::Shape; - -use std::sync::Arc; -use crate::material::Material; -use crate::core::{Bound3f, Ray}; - -pub struct Object { - pub shape: Shape, - pub mat: Arc<dyn Material>, -} - -impl Object { - pub fn new<T: Into<Shape>>(mat: Arc<dyn Material>, shape: T) -> Self { - Object { - mat, - shape: shape.into(), - } - } -} - -impl Hittable for Object { - fn intersect(&self, ray: &Ray) -> Option<Intersection> { - if let Some(mut inter) = self.shape.intersect(ray) { - inter.add_material_if_none(self.mat.as_ref()); - Some(inter) - } else { - None - } - } - - fn bounding_box(&self) -> Bound3f { - self.shape.bounding_box() - } -} diff --git a/src/world/scene.rs b/src/world/scene.rs deleted file mode 100644 index 87bec1f..0000000 --- a/src/world/scene.rs +++ /dev/null @@ -1,43 +0,0 @@ -use crate::core::{Bound3f, Ray}; - -use super::{Object, container, Hittable, Intersection}; - -type Container = container::HittableList; - -pub struct Scene { - content: Container, -} - -impl Scene { - pub fn new() -> Self { - Self::default() - } - - pub fn add_object(&mut self, obj: Object) { - self.content.add(obj); - } - - pub fn add_objects(&mut self, objs: Vec<Object>) { - for obj in objs { - self.add_object(obj); - } - } -} - -impl Hittable for Scene { - fn intersect(&self, ray: &Ray) -> Option<Intersection> { - self.content.intersect(ray) - } - - fn bounding_box(&self) -> Bound3f { - self.content.bounding_box() - } -} - -impl Default for Scene { - fn default() -> Self { - Self { - content: Container::new(), - } - } -} diff --git a/src/world/shapes/mod.rs b/src/world/shapes/mod.rs deleted file mode 100644 index a11df5d..0000000 --- a/src/world/shapes/mod.rs +++ /dev/null @@ -1,30 +0,0 @@ -mod sphere; - -pub use sphere::Sphere; - -use crate::world::{Hittable, Intersection}; -use crate::core::{Bound3f, Ray}; - -pub enum Shape { - Sphere(Sphere), -} - -impl Hittable for Shape { - fn intersect(&self, ray: &Ray) -> Option<Intersection> { - match self { - Self::Sphere(sph) => sph.intersect(ray) - } - } - - fn bounding_box(&self) -> Bound3f { - match self { - Self::Sphere(sph) => sph.bounding_box() - } - } -} - -impl From<Sphere> for Shape { - fn from(s: Sphere) -> Self { - Self::Sphere(s) - } -} diff --git a/src/world/shapes/sphere.rs b/src/world/shapes/sphere.rs deleted file mode 100644 index 1df9c35..0000000 --- a/src/world/shapes/sphere.rs +++ /dev/null @@ -1,98 +0,0 @@ -//! Implements sphere -//! -//! Spheres are relatively easy to calculate intersections between -use crate::{Float, NEAR_ZERO}; -use crate::core::{Ray, Vector3f, Bound3f}; -use crate::world::{Hittable, Intersection}; - -pub struct Sphere { - radius: Float, - center: Vector3f, -} - -impl Sphere { - pub fn new(radius: Float, center: Vector3f) -> Sphere { - Sphere { - radius, - center, - } - } - - fn norm_at(&self, point: &Vector3f) -> Vector3f { - let mut v = *point - self.center; - v /= self.radius; - v - } -} - -impl Hittable for Sphere { - // Implementation from ray tracing in a weekend - fn intersect(&self, ray: &Ray) -> Option<Intersection> { - let oc = ray.origin - self.center; - let a = ray.direction.len_squared(); - let half_b = oc.dot(&ray.direction); - let c = oc.len_squared() - self.radius * self.radius; - let disc = half_b*half_b - a*c; - - if disc < 0.0 { - None - } else { - let disc_sqrt = disc.sqrt(); - - let mut distance = -half_b - disc_sqrt; - if distance <= NEAR_ZERO { - distance = -half_b + disc_sqrt; - } - if distance <= NEAR_ZERO { - return None; - } - - distance /= a; - let w = ray.at(distance); - Some(Intersection::new( - self.norm_at(&w), - w, - ray, - distance, - )) - } - - } - - /// Box containing the circle - /// - /// # Examples - /// - /// ``` - /// use rendering::core::Vector3f; - /// use rendering::world::{Hittable, shapes::Sphere}; - /// - /// let sph = Sphere::new(1.0, Vector3f::new(0.0)); - /// let b = sph.bounding_box(); - /// - /// assert!(b.min.x == -1.0 && b.min.y == -1.0 && b.min.z == -1.0); - /// assert!(b.max.x == 1.0 && b.max.y == 1.0 && b.max.z == 1.0); - fn bounding_box(&self) -> Bound3f { - let offset = Vector3f::new(self.radius); - - Bound3f::new(self.center - offset, self.center + offset) - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn sphere_intersect() { - let sph = Sphere::new(2.0, Vector3f::new_xyz(2.0, 3.0, 4.0)); - - let ray = Ray { - origin: Vector3f::new_xyz(1.0, 0.0, 0.0), - direction: Vector3f::new_xyz(0.0, 1.0, 1.5).norm(), - }; - - let dist = sph.intersect(&ray).unwrap(); - assert!((dist.t - 3.28).abs() < 0.01); - } -} |