diff --git a/Cargo.lock b/Cargo.lock index a9b28e1..0684b30 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,11 +1,36 @@ +[[package]] +name = "ansi_term" +version = "0.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "aobench" version = "0.1.0" dependencies = [ "log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)", + "rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)", "stderrlog 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "atty" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "bitflags" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "cfg-if" version = "0.1.4" @@ -21,6 +46,42 @@ dependencies = [ "time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "clap" +version = "2.32.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)", + "atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)", + "textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", + "vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "cloudabi" +version = "0.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "fuchsia-zircon-sys" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "lazy_static" version = "1.0.2" @@ -52,11 +113,52 @@ name = "num-traits" version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "proc-macro2" +version = "0.4.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "quote" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand" +version = "0.5.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)", + "fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)", + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)", + "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "rand_core" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "redox_syscall" version = "0.1.40" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "redox_termios" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "stderrlog" version = "0.4.1" @@ -68,6 +170,40 @@ dependencies = [ "thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "strsim" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "structopt" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "structopt-derive" +version = "0.2.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "syn" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)", + "unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "termcolor" version = "0.3.6" @@ -76,6 +212,24 @@ dependencies = [ "wincolor 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "termion" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)", + "redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "textwrap" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "thread_local" version = "0.3.5" @@ -95,6 +249,16 @@ dependencies = [ "winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "unicode-width" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] +name = "unicode-xid" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "unreachable" version = "1.0.0" @@ -103,6 +267,11 @@ dependencies = [ "void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "vec_map" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "void" version = "1.0.2" @@ -136,19 +305,40 @@ dependencies = [ ] [metadata] +"checksum ansi_term 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ee49baf6cb617b853aa8d93bf420db2383fab46d314482ca2803b40d5fde979b" +"checksum atty 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "2fc4a1aa4c24c0718a250f0681885c1af91419d242f29eb8f2ab28502d80dbd1" +"checksum bitflags 1.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "d0c54bb8f454c567f21197eefcdbf5679d0bd99f2ddbe52e84c77061952e6789" "checksum cfg-if 0.1.4 (registry+https://github.com/rust-lang/crates.io-index)" = "efe5c877e17a9c717a0bf3613b2709f723202c4e4675cc8f12926ded29bcb17e" "checksum chrono 0.4.4 (registry+https://github.com/rust-lang/crates.io-index)" = "6962c635d530328acc53ac6a955e83093fedc91c5809dfac1fa60fa470830a37" +"checksum clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)" = "b957d88f4b6a63b9d70d5f454ac8011819c6efa7727858f458ab71c756ce2d3e" +"checksum cloudabi 0.0.3 (registry+https://github.com/rust-lang/crates.io-index)" = "ddfc5b9aa5d4507acaf872de71051dfd0e309860e88966e1051e462a077aac4f" +"checksum fuchsia-zircon 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "2e9763c69ebaae630ba35f74888db465e49e259ba1bc0eda7d06f4a067615d82" +"checksum fuchsia-zircon-sys 0.3.3 (registry+https://github.com/rust-lang/crates.io-index)" = "3dcaa9ae7725d12cdb85b3ad99a434db70b468c09ded17e012d86b5c1010f7a7" "checksum lazy_static 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "fb497c35d362b6a331cfd94956a07fc2c78a4604cdbee844a81170386b996dd3" "checksum libc 0.2.42 (registry+https://github.com/rust-lang/crates.io-index)" = "b685088df2b950fccadf07a7187c8ef846a959c142338a48f9dc0b94517eb5f1" "checksum log 0.4.3 (registry+https://github.com/rust-lang/crates.io-index)" = "61bd98ae7f7b754bc53dca7d44b604f733c6bba044ea6f41bc8d89272d8161d2" "checksum num-integer 0.1.39 (registry+https://github.com/rust-lang/crates.io-index)" = "e83d528d2677f0518c570baf2b7abdcf0cd2d248860b68507bdcb3e91d4c0cea" "checksum num-traits 0.2.5 (registry+https://github.com/rust-lang/crates.io-index)" = "630de1ef5cc79d0cdd78b7e33b81f083cbfe90de0f4b2b2f07f905867c70e9fe" +"checksum proc-macro2 0.4.9 (registry+https://github.com/rust-lang/crates.io-index)" = "cccdc7557a98fe98453030f077df7f3a042052fae465bb61d2c2c41435cfd9b6" +"checksum quote 0.6.3 (registry+https://github.com/rust-lang/crates.io-index)" = "e44651a0dc4cdd99f71c83b561e221f714912d11af1a4dff0631f923d53af035" +"checksum rand 0.5.4 (registry+https://github.com/rust-lang/crates.io-index)" = "12397506224b2f93e6664ffc4f664b29be8208e5157d3d90b44f09b5fae470ea" +"checksum rand_core 0.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "edecf0f94da5551fc9b492093e30b041a891657db7940ee221f9d2f66e82eef2" "checksum redox_syscall 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "c214e91d3ecf43e9a4e41e578973adeb14b474f2bee858742d127af75a0112b1" +"checksum redox_termios 0.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "7e891cfe48e9100a70a3b6eb652fef28920c117d366339687bd5576160db0f76" "checksum stderrlog 0.4.1 (registry+https://github.com/rust-lang/crates.io-index)" = "61dc66b7ae72b65636dbf36326f9638fb3ba27871bb737a62e2c309b87d91b70" +"checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum structopt 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "d8e9ad6a11096cbecdcca0cc6aa403fdfdbaeda2fb3323a39c98e6a166a1e45a" +"checksum structopt-derive 0.2.10 (registry+https://github.com/rust-lang/crates.io-index)" = "4cbce8ccdc62166bd594c14396a3242bf94c337a51dbfa9be1076dd74b3db2af" +"checksum syn 0.14.4 (registry+https://github.com/rust-lang/crates.io-index)" = "2beff8ebc3658f07512a413866875adddd20f4fd47b2a4e6c9da65cd281baaea" "checksum termcolor 0.3.6 (registry+https://github.com/rust-lang/crates.io-index)" = "adc4587ead41bf016f11af03e55a624c06568b5a19db4e90fde573d805074f83" +"checksum termion 1.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "689a3bdfaab439fd92bc87df5c4c78417d3cbe537487274e9b0b2dce76e92096" +"checksum textwrap 0.10.0 (registry+https://github.com/rust-lang/crates.io-index)" = "307686869c93e71f94da64286f9a9524c0f308a9e1c87a583de8e9c9039ad3f6" "checksum thread_local 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "279ef31c19ededf577bfd12dfae728040a21f635b06a24cd670ff510edd38963" "checksum time 0.1.40 (registry+https://github.com/rust-lang/crates.io-index)" = "d825be0eb33fda1a7e68012d51e9c7f451dc1a69391e7fdc197060bb8c56667b" +"checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" +"checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" +"checksum vec_map 0.8.1 (registry+https://github.com/rust-lang/crates.io-index)" = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" "checksum void 1.0.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" "checksum winapi 0.3.5 (registry+https://github.com/rust-lang/crates.io-index)" = "773ef9dcc5f24b7d850d0ff101e542ff24c3b090a9768e03ff889fdef41f00fd" "checksum winapi-i686-pc-windows-gnu 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)" = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" diff --git a/Cargo.toml b/Cargo.toml index e5b917e..6021430 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,3 +6,5 @@ authors = ["Bill Thiede "] [dependencies] log = "0.4" stderrlog = "0.4.1" +structopt = "0.2.10" +rand = "0.5" diff --git a/src/main.rs b/src/main.rs index cbe9941..eb4195b 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,16 +1,119 @@ #[macro_use] extern crate log; +extern crate rand; extern crate stderrlog; +#[macro_use] +extern crate structopt; +use rand::Rng; +use std::clone::Clone; +use std::f64; +use std::fmt; use std::fs::File; use std::io::Write; +use std::ops::Add; +use std::ops::Mul; +use std::ops::Sub; +use structopt::StructOpt; +fn vdot(v0: &Vector, v1: &Vector) -> f64 { + v0.x * v1.x + v0.y * v1.y + v0.z * v1.z +} + +fn vcross(v0: &Vector, v1: &Vector) -> Vector { + Vector { + x: v0.y * v1.z - v0.z * v1.y, + y: v0.z * v1.x - v0.x * v1.z, + z: v0.x * v1.y - v0.y * v1.x, + } +} + +#[derive(Debug, Copy, Clone)] struct Vector { x: f64, y: f64, z: f64, } +impl Vector { + fn zero() -> Vector { + Vector { + x: 0., + y: 0., + z: 0., + } + } + fn new(x: f64, y: f64, z: f64) -> Vector { + Vector { x, y, z } + } + + fn normalize(&mut self) { + let length = vdot(self, self).sqrt(); + if length.abs() < 1.0e-17 { + return; + } + + self.x = self.x / length; + self.y = self.y / length; + self.z = self.z / length; + } +} + +impl fmt::Display for Vector { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "({:.4}, {:.4}, {:.4})", self.x, self.y, self.z) + } +} + +impl Add for Vector { + type Output = Vector; + + fn add(self, other: Vector) -> Vector { + Vector { + x: self.x + other.x, + y: self.y + other.y, + z: self.z + other.z, + } + } +} + +impl Mul for Vector { + type Output = Vector; + + fn mul(self, other: Vector) -> Vector { + Vector { + x: self.x * other.x, + y: self.y * other.y, + z: self.z * other.z, + } + } +} + +impl Mul for Vector { + type Output = Vector; + + fn mul(self, other: f64) -> Vector { + Vector { + x: self.x * other, + y: self.y * other, + z: self.z * other, + } + } +} + +impl Sub for Vector { + type Output = Vector; + + fn sub(self, other: Vector) -> Vector { + Vector { + x: self.x - other.x, + y: self.y - other.y, + z: self.z - other.z, + } + } +} + +#[derive(Copy, Clone)] struct ISect { t: f64, p: Vector, @@ -28,102 +131,281 @@ struct Plane { n: Vector, } +#[derive(Debug)] struct Ray { org: Vector, dir: Vector, } +impl fmt::Display for Ray { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "o: {} d: {}", self.org, self.dir) + } +} + +fn ray_sphere_intersect(mut isect: ISect, ray: &Ray, sphere: &Sphere) -> ISect { + let rs = ray.org - sphere.center; + + let b = vdot(&rs, &ray.dir); + let c = vdot(&rs, &rs) - sphere.radius * sphere.radius; + let d = b * b - c; + if d > 0.0 { + let t = -b - d.sqrt(); + if t > 0.0 && t < isect.t { + isect.t = t; + isect.hit = true; + + isect.p = ray.org + ray.dir * t; + + isect.n = isect.p - sphere.center; + isect.n.normalize(); + } + } + + isect +} + +fn ray_plane_intersect(mut isect: ISect, ray: &Ray, plane: &Plane) -> ISect { + let d = -vdot(&plane.p, &plane.n); + let v = vdot(&ray.dir, &plane.n); + + if v.abs() < 1.0e-17 { + return isect; + } + + let t = -(vdot(&ray.org, &plane.n) + d) / v; + + if (t > 0.0) && (t < isect.t) { + isect.t = t; + isect.hit = true; + + isect.p = ray.org + ray.dir * t; + isect.n = plane.n; + } + isect +} + struct World { spheres: Vec, plane: Plane, width: usize, height: usize, - n_subsamples: u32, - n_aosamples: u32, + n_subsamples: usize, + n_aosamples: usize, +} - img: Vec, +fn ortho_basis(n: &Vector) -> [Vector; 3] { + let mut basis: [Vector; 3] = [Vector::zero(), Vector::zero(), n.clone()]; + + if (n.x < 0.6) && (n.x > -0.6) { + basis[1].x = 1.0; + } else if (n.y < 0.6) && (n.y > -0.6) { + basis[1].y = 1.0; + } else if (n.z < 0.6) && (n.z > -0.6) { + basis[1].z = 1.0; + } else { + basis[1].x = 1.0; + } + + basis[0] = vcross(&basis[1], &basis[2]); + basis[0].normalize(); + + basis[1] = vcross(&basis[2], &basis[0]); + basis[1].normalize(); + + basis } impl World { - fn render(&mut self) { - info!( - "Rendering world {}x{} w/ {} subsampling and {} ambient occlusion sampling", - self.width, self.height, self.n_subsamples, self.n_aosamples - ); - for y in 0..self.height { - for x in 0..self.width { - self.img[(x + y * self.width) * 3 + 0] = (x % 256) as u8; - self.img[(x + y * self.width) * 3 + 1] = (y % 256) as u8; - self.img[(x + y * self.width) * 3 + 2] = ((x * y) % 256) as u8; + fn ambient_occlusion(&self, isect: ISect) -> Vector { + let n_theta: usize = self.n_aosamples; + let n_phi: usize = self.n_aosamples; + let eps: f64 = 0.0001; + + let p = isect.p + isect.n * eps; + let basis = ortho_basis(&isect.n); + let mut occlusion: f64 = 0.0; + let mut rng = rand::thread_rng(); + for _ in 0..n_theta { + for _ in 0..n_phi { + let theta: f64 = rng.gen::().sqrt(); + let phi: f64 = rng.gen::() * 2.0 * f64::consts::PI; + + let x = phi.cos() * theta; + let y = phi.sin() * theta; + let z = (1.0 - theta * theta).sqrt(); + + let rx = x * basis[0].x + y * basis[1].x + z * basis[2].x; + let ry = x * basis[0].y + y * basis[1].y + z * basis[2].y; + let rz = x * basis[0].z + y * basis[1].z + z * basis[2].z; + + let ray = Ray { + org: p.clone(), + dir: Vector::new(rx, ry, rz), + }; + + let mut occ_isect = ISect { + t: 1.0e+17, + hit: false, + p: Vector::zero(), + n: Vector::zero(), + }; + + occ_isect = ray_sphere_intersect(occ_isect, &ray, &self.spheres[0]); + occ_isect = ray_sphere_intersect(occ_isect, &ray, &self.spheres[1]); + occ_isect = ray_sphere_intersect(occ_isect, &ray, &self.spheres[2]); + occ_isect = ray_plane_intersect(occ_isect, &ray, &self.plane); + + if occ_isect.hit { + occlusion += 1.0; + } } } - } - - fn save(&self, path: &str) -> std::io::Result<()> { - info!("Saving scene to {}", path); - let mut buf = File::create(path)?; - writeln!(&buf, "P6\n{} {}\n255", self.width, self.height)?; - buf.write_all(&self.img) + let n_theta = n_theta as f64; + let n_phi = n_phi as f64; + occlusion = (n_theta * n_phi - occlusion) / (n_theta * n_phi); + Vector::new(occlusion, occlusion, occlusion) } } -fn init_scene() -> World { - let w: usize = 512; - let h: usize = 512; +fn clamp(f: f64) -> u8 { + let i = (f * 255.5) as i32; + if i < 0 { + return 0; + } + if i > 255 { + return 255; + } + i as u8 +} + +impl World { + fn render(&mut self) -> Vec { + let w = self.width; + let h = self.height; + info!( + "Rendering world {}x{} w/ {} subsampling and {} ambient occlusion sampling", + w, h, self.n_subsamples, self.n_aosamples + ); + let mut img = vec![0; w * h * 3]; + let mut fimg = vec![0.0; w * h * 3]; + for y in 0..h as usize { + for x in 0..w as usize { + for v in 0..self.n_subsamples { + for u in 0..self.n_subsamples { + let fx = x as f64; + let fy = y as f64; + let fw = w as f64; + let fh = h as f64; + let fu = u as f64; + let fv = v as f64; + let n_subsamples = self.n_subsamples as f64; + let px = (fx + (fu / n_subsamples) - (fw / 2.0)) / (fw / 2.); + let py = -(fy + (fv / n_subsamples) - (fh / 2.0)) / (fh / 2.); + + let mut dir = Vector::new(px, py, -1.0); + dir.normalize(); + + let ray = Ray { + org: Vector::zero(), + dir, + }; + let mut isect = ISect { + t: 1.0e+17, + hit: false, + p: Vector::zero(), + n: Vector::zero(), + }; + + isect = ray_sphere_intersect(isect, &ray, &self.spheres[0]); + + isect = ray_sphere_intersect(isect, &ray, &self.spheres[1]); + isect = ray_sphere_intersect(isect, &ray, &self.spheres[2]); + isect = ray_plane_intersect(isect, &ray, &self.plane); + + if isect.hit { + let col = self.ambient_occlusion(isect); + fimg[(x + y * w) * 3 + 0] += col.x; + fimg[(x + y * w) * 3 + 1] += col.y; + fimg[(x + y * w) * 3 + 2] += col.z; + } + } + } + let n2 = (self.n_subsamples * self.n_subsamples) as f64; + fimg[3 * (y * w + x) + 0] /= n2; + fimg[3 * (y * w + x) + 1] /= n2; + fimg[3 * (y * w + x) + 2] /= n2; + + img[3 * (y * w + x) + 0] = clamp(fimg[3 * (y * w + x) + 0]); + img[3 * (y * w + x) + 1] = clamp(fimg[3 * (y * w + x) + 1]); + img[3 * (y * w + x) + 2] = clamp(fimg[3 * (y * w + x) + 2]); + } + } + img + } +} + +fn save(width: usize, height: usize, img: &[u8], path: &str) -> std::io::Result<()> { + info!("Saving scene to {}", path); + let mut buf = File::create(path)?; + writeln!(&buf, "P6\n{} {}\n255", width, height)?; + buf.write_all(img) +} + +fn init_scene(width: usize, height: usize, n_subsamples: usize, n_aosamples: usize) -> World { World { spheres: vec![ Sphere { - center: Vector { - x: -2.0, - y: 0.0, - z: -3.5, - }, + center: Vector::new(-2.0, 0.0, -3.5), radius: 0.5, }, Sphere { - center: Vector { - x: -0.5, - y: 0.0, - z: -3.0, - }, + center: Vector::new(-0.5, 0.0, -3.0), radius: 0.5, }, Sphere { - center: Vector { - x: 1.0, - y: 0.0, - z: -2.2, - }, + center: Vector::new(1.0, 0.0, -2.2), radius: 0.5, }, ], plane: Plane { - p: Vector { - x: 0.0, - y: -0.5, - z: 0.0, - }, - n: Vector { - x: 0.0, - y: 1.0, - z: 0.0, - }, + p: Vector::new(0.0, -0.5, 0.0), + n: Vector::new(0.0, 1.0, 0.0), }, - width: w, - height: h, - n_subsamples: 2, - n_aosamples: 8, - - img: vec![0; w * h * 3], + width, + height, + n_subsamples, + n_aosamples, } } -fn main() -> std::io::Result<()> { - stderrlog::new().verbosity(3).init().unwrap(); - - let mut w = init_scene(); - w.render(); - w.save("ao.ppm") +#[derive(StructOpt, Debug)] +#[structopt()] +struct Opt { + /// Verbose mode (-v, -vv, -vvv, etc) + #[structopt(short = "v", long = "verbose", parse(from_occurrences), default_value = "vvv")] + verbose: usize, + #[structopt(short = "w", long = "width", default_value = "512")] + width: usize, + #[structopt(short = "h", long = "height", default_value = "512")] + height: usize, + #[structopt(short = "s", long = "subsamples", default_value = "2")] + n_subsamples: usize, + #[structopt(short = "a", long = "aosamples", default_value = "8")] + n_aosamples: usize, +} + +fn main() -> std::io::Result<()> { + let opt = Opt::from_args(); + stderrlog::new() + .verbosity(opt.verbose) + .timestamp(stderrlog::Timestamp::Millisecond) + .init() + .unwrap(); + + let mut w = init_scene(opt.width, opt.height, opt.n_subsamples, opt.n_aosamples); + let img = w.render(); + save(opt.width, opt.height, &img, "ao.ppm") }