diff --git a/rustrays/README.md b/rustrays/README.md new file mode 100644 index 0000000..36e4ad0 --- /dev/null +++ b/rustrays/README.md @@ -0,0 +1,17 @@ +# rustrays + +This is a Rust port of [the Go port][1] of [the business card raytracer][2]. + +## Prerequisites + + * rust 0.9 + +## Usage + + $ rustc --opt-level 3 rustrays/rays.rs + $ rustrays/rays + $ open render.ppm + + +[1]: https://github.com/kidoman/rays/tree/master/gorays +[2]: https://gist.github.com/kid0m4n/6680629 diff --git a/rustrays/art.rs b/rustrays/art.rs new file mode 100644 index 0000000..8f75c06 --- /dev/null +++ b/rustrays/art.rs @@ -0,0 +1,39 @@ +use vector::Vector; +use std::path::Path; +use std::io::fs::File; +use std::io::buffered::BufferedReader; + +pub struct Art(~[~str]); + +pub fn readArt(artfile: &str) -> Art { + let mut lines : ~[~str] = ~[]; + let mut file = BufferedReader::new(File::open(&Path::new(artfile))); + for line in file.lines() { + lines.push(line.trim_right().to_owned()); + } + Art(lines) +} + +impl Art { + pub fn objects(&self) -> ~[Vector] { + let lines: &~[~str] = match self { + &Art(ref a) => a + }; + + let mut objects: ~[Vector] = ~[]; + let mut j = 0; + for ref line in lines.iter() { + for (k, ref char) in line.char_indices() { + match *char { + ' ' => { }, + _ => { + let v = Vector {x: k as f64, y: 6.5, z: -((lines.len()-j) as f64) - 1.0 }; + objects.push(v); + } + } + } + j += 1; + } + objects + } +} diff --git a/rustrays/camera.rs b/rustrays/camera.rs new file mode 100644 index 0000000..9a78d1a --- /dev/null +++ b/rustrays/camera.rs @@ -0,0 +1,21 @@ +use vector::Vector; + +pub struct Camera { + dir: Vector, + up: Vector, + right: Vector, + eyeOffset: Vector, + ar: f64 +} + +impl Camera { + pub fn new(dir: Vector, size: int) -> Camera { + let dir = dir.normalize(); + let up = Vector { x: 0.0, y: 0.0, z: 1.0 }.crossProduct(dir).normalize().scale(0.002); + let right = dir.crossProduct(up).normalize().scale(0.002); + let eyeOffset = up.add(&right).scale(-256.0).add(&dir); + let ar = 512.0 / (size as f64); + + Camera { dir: dir, up: up, right: right, eyeOffset: eyeOffset, ar: ar } + } +} diff --git a/rustrays/image.rs b/rustrays/image.rs new file mode 100644 index 0000000..bf8c3cc --- /dev/null +++ b/rustrays/image.rs @@ -0,0 +1,26 @@ +use std::vec; +use std::io::fs::File; +use std::path::Path; + +pub struct Image { + size: int, + data: ~[u8] +} + +impl Image { + pub fn new(size: int) -> Image { + let cap = (3 * size * size) as uint; + let mut vec = vec::with_capacity(cap); + unsafe { + vec.set_len(cap); + } + return Image { size: size, data: vec } + } + + pub fn save(&self, outputfile: &str) { + let mut file = File::create(&Path::new(outputfile)); + let s = format!("P6 {} {} 255 ", self.size, self.size); + file.write(s.as_bytes()); + file.write(self.data); + } +} diff --git a/rustrays/misc.rs b/rustrays/misc.rs new file mode 100644 index 0000000..e3dc81a --- /dev/null +++ b/rustrays/misc.rs @@ -0,0 +1,19 @@ +pub unsafe fn rnd(s: *mut u32) -> f64 { + let mut ss : u32 = *s; + ss += ss; + ss ^= 1; + + if (ss as i32) < 0 { + ss ^= 0x88888eef; + } + *s = ss; + + return ((*s%95) as f64) / 95.0; +} + +pub fn clamp(v: f64) -> u8 { + if v > 255.0 { + return 255u8 + } + return v as u8; +} diff --git a/rustrays/rays.rs b/rustrays/rays.rs new file mode 100644 index 0000000..7c0db92 --- /dev/null +++ b/rustrays/rays.rs @@ -0,0 +1,136 @@ +#[feature(globs)]; +extern mod native; +extern mod extra; + +use extra::getopts::*; +use std::os; +use std::path::Path; +use extra::time::precise_time_ns; +use extra::arc::Arc; +use extra::comm::DuplexStream; + +use camera::Camera; +use result::Result; +use worker::Worker; +use image::Image; +use vector::Vector; +use art::{Art,readArt}; + +mod art; +mod image; +mod camera; +mod vector; +mod result; +mod worker; +mod misc; + +#[start] +fn start(argc: int, argv: **u8) -> int { + native::start(argc, argv, main) +} + +fn main() { + let args = os::args(); + let opts = ~[ + optflagopt("m"), + optflagopt("t"), + optflagopt("p"), + optflagopt("o"), + optflagopt("r"), + optflagopt("a"), + optflagopt("h") + ]; + + let matches = match getopts(args.tail(), opts) { + Ok(m) => { m } + Err(f) => { fail!(f.to_err_msg()) } + }; + + let mp = match matches.opt_str("m") { + Some(s) => { std::num::FromStrRadix::from_str_radix(s, 10).unwrap() } + None => { 1f32 } + }; + + let size = std::f32::sqrt(mp * 1000000f32) as int; + + let times = match matches.opt_str("t") { + Some(s) => { std::num::FromStrRadix::from_str_radix(s, 10).unwrap() } + None => { 1 } + }; + println(format!("Will render {} time(s)", times)); + + let procs = match matches.opt_str("p") { + Some(s) => { std::num::FromStrRadix::from_str_radix(s, 10).unwrap() } + None => { std::rt::default_sched_threads() as int } + }; + if procs < 1 { + fail!("procs ({}) needs to be >= 1", procs) + } + + let outputfile = match matches.opt_str("o") { + Some(s) => { s } + None => { ~"render.ppm" } + }; + + let resultfile = match matches.opt_str("r") { + Some(s) => { s } + None => { ~"result.json" } + }; + + let home = match matches.opt_str("h") { + Some(s) => { s } + None => { std::os::getenv("RAYS_HOME").unwrap_or_default() } + }; + + let mut artfile = match matches.opt_str("a") { + Some(s) => { s } + None => { ~"ART" } + }; + + if artfile == ~"ART" { + let path = Path::new(home).join(artfile); + artfile = path.as_str().unwrap().into_owned(); + } + + let ar : Art = readArt(artfile); + let objects_arc = Arc::new(ar.objects()); + + let mut result = Result { samples: ~[] }; + let image : *mut Image = &mut Image::new(size); + + for t in range(0, times) { + print(format!("Starting render\\#{0} of size {1} MP ({2}x{3}) with {4} task(s).", t+1, mp, size, size, procs)); + std::io::stdio::flush(); + let startTime = precise_time_ns(); + + let cam = Camera::new(Vector { x: -3.1, y: -16.0, z: 1.9 }, size); + + let mut chans = ~[]; + + for i in range(0, procs) { + let (main, worker) = DuplexStream::new(); + worker.send(objects_arc.clone()); + chans.push(worker); + + do spawn { + let local_arc : Arc<~[Vector]> = main.recv(); + Worker { id: i, cam: cam, image: image, objects: local_arc.get() }.render(procs as uint); + main.send(()); + } + } + chans.iter().advance( |worker| { + worker.recv(); + true + }); + + let duration = (precise_time_ns() - startTime) as f64 / 1e9; + result.samples.push(duration); + println(format!(" Time taken for render {}", duration)); + } + + println(format!("Average time {}", result.average())); + result.save(resultfile); + unsafe { + (*image).save(outputfile); + } +} \ No newline at end of file diff --git a/rustrays/result.rs b/rustrays/result.rs new file mode 100644 index 0000000..51c1ae6 --- /dev/null +++ b/rustrays/result.rs @@ -0,0 +1,42 @@ +extern mod extra; + +use std::io::fs::File; +use extra::json::PrettyEncoder; +use extra::serialize::Encodable; + +pub struct Result { + samples: ~[f64] +} + +#[deriving(Encodable)] +struct JsonResult<'a> { + average: f64, + samples: &'a[f64] +} + +impl Result { + pub fn sum(&self) -> f64 { + let mut sum = 0.0; + for &s in self.samples.iter() { + sum += s; + } + sum + } + + pub fn average(&self) -> f64 { + self.sum() / self.samples.len() as f64 + } + + pub fn save(&self, resultfile: &str) { + match File::create(&Path::new(resultfile)) { + Some(ref mut file) => { + let mut enc = PrettyEncoder::new(file); + JsonResult { average: self.average(), samples: self.samples }.encode(&mut enc); + file.flush(); + } + None => { + println(format!("Opening {} file failed!", resultfile)); + } + }; + } +} diff --git a/rustrays/vector.rs b/rustrays/vector.rs new file mode 100644 index 0000000..5958ef4 --- /dev/null +++ b/rustrays/vector.rs @@ -0,0 +1,36 @@ +use std::to_str::ToStr; +use std::num::rsqrt; + +pub struct Vector { + x: f64, + y: f64, + z: f64 +} + +impl Vector { + pub fn add(&self, r: &Vector) -> Vector { + Vector { x: r.x + self.x, y: r.y + self.y, z: r.z + self.z } + } + + pub fn scale(&self, r: f64) -> Vector { + Vector { x: self.x * r, y: self.y * r, z: self.z * r } + } + + pub fn dotProduct(&self, r: &Vector) -> f64 { + return self.x*r.x + self.y*r.y + self.z*r.z + } + + pub fn crossProduct(&self, r: Vector) -> Vector { + Vector { x: self.y * r.z - self.z * r.y, y: self.z * r.x - self.x * r.z, z: self.x * r.y - self.y * r.x } + } + + pub fn normalize(&self) -> Vector { + self.scale(rsqrt(self.x * self.x + self.y * self.y + self.z * self.z)) + } +} + +impl ToStr for Vector { + fn to_str(&self) -> ~str { + format!("Vector [x={}, y={}, z={}]", self.x, self.y, self.z) + } +} diff --git a/rustrays/worker.rs b/rustrays/worker.rs new file mode 100644 index 0000000..36af21d --- /dev/null +++ b/rustrays/worker.rs @@ -0,0 +1,168 @@ +use std; +use camera::Camera; +use image::Image; +use vector::Vector; +use misc::{rnd, clamp}; + +pub struct Worker<'a> { + id: int, + cam: Camera, + image: *mut Image, + objects: &'a~[Vector] +} + +impl<'a> Worker<'a> { + pub fn render(&'a mut self, procs: uint) { + let mut s = std::rand::random::(); + let seed = &mut s; + + let (image_data, size) = unsafe { + (&mut (*self.image).data, (*self.image).size) + }; + + let mut y = self.id; + while y < size { + let mut k = (size - y - 1) * 3 * size; + for x in range(0, size).invert() { + let mut p = Vector { x: 13.0, y: 13.0, z: 13.0 }; + for _ in range(0, 64) { + unsafe { + let t = self.cam.up.scale(rnd(seed) - 0.5) + .scale(99.0) + .add(&self.cam.right.scale(rnd(seed) - 0.5).scale(99.0)); + + let orig = Vector { x: -5.0, y: 16.0, z: 8.0 }.add(&t); + + let dir = t.scale(-1.0) + .add( + &self.cam.up.scale(rnd(seed) + (x as f64)*self.cam.ar) + .add(&self.cam.right.scale(rnd(seed) + (y as f64)*self.cam.ar)) + .add(&self.cam.eyeOffset).scale(16.0) + ) + .normalize(); + p = sampler(&orig, &dir, seed, self.objects).scale(3.5).add(&p); + } + } + + image_data[k] = clamp(p.x); + k+=1; + + image_data[k] = clamp(p.y); + k+=1; + + image_data[k] = clamp(p.z); + k+=1; + } + y += procs as int; + } + } +} + +fn sampler(orig: &Vector, dir: &Vector, seed: *mut u32, objects: &~[Vector]) -> Vector { + let (st, dist, bounce) = tracer(orig, dir, objects); + let obounce = bounce; + + // If we hit the sky, early return! + match st { + MissUpward => { + let p = 1.0 - dir.z; + return Vector { x: 1.0, y: 1.0, z: 1.0 }.scale(p) + }, + _ => {} + } + + // Intersection coordinate. + let mut h = orig.add(&dir.scale(dist)); + // Director of light (+ random delta for soft shadows.) + let l = unsafe { + Vector { x: 9.0 + rnd(seed), y: 9.0 + rnd(seed), z: 16.0 }.add(&h.scale(-1.0)).normalize() + }; + + // Lambertian factor. + let mut b = l.dotProduct(&bounce); + + let mut sf = 1.0; + if b < 0.0 { + b = 0.0; + sf = 0.0; + } else { + //let mut st : Status = MissUpward; + let (st, _, _) = tracer(&h, &l, objects); + match st { + MissUpward => {}, + _ => { + b = 0.0; + sf = 0.0; + } + } + } + + // If we hit the ground, early return! + match st { + MissDownward => { + h = h.scale(0.2); + let mut fc = Vector {x: 3.0, y: 3.0, z: 3.0 }; + if ((h.x.ceil() + h.y.ceil()) as int ) & 1 == 1 { + fc = Vector { x: 3.0, y: 1.0, z: 1.0 }; + } + return fc.scale(b*0.2 + 0.1) + }, + _ => {} + } + + // Half vector. + let r = dir.add(&obounce.scale(obounce.dotProduct(&dir.scale(-2.0)))); + + // Calculate the color p. + let p = std::f64::pow(l.dotProduct(&r.scale(sf)), 99.0); + + // Recursively trace the path along, always scaling the next bounce by a factor of 0.5 + Vector { x: p, y: p, z: p }.add(&sampler(&h, &r, seed, objects).scale(0.5)) +} + +pub enum Status { + MissUpward, // Hit the sky? + MissDownward, // Hit the ground + Hit // Hit an object +} + +// Tracer calculates the minimum distance to a intersecting (object, ground, sky) along with the bounce vector +// from the said object. +fn tracer(orig: &Vector, dir: &Vector, objects: &~[Vector]) -> (Status, f64, Vector) { + // First case (assumption) is that we will hit the sky. + let mut st = MissUpward; + let mut dist = 1e9; + let mut bounce = Vector { x: 0.0, y: 0.0, z: 0.0 }; + + // If we are pointing towards the ground: + let p = -orig.z / dir.z; + if 0.01 < p { + st = MissDownward; + dist = p; + bounce = Vector { x: 0.0, y: 0.0, z: 1.0 }; + } + + // Iterate through all the objects checking if there is a possible hit. + for o in objects.iter() { + // The sphere location is in o + let p = orig.add(o); + let b = p.dotProduct(dir); + + let b2 = b * b; + let c = p.dotProduct(&p) - 1.0; + + if b2 > c { + let q = b2 - c; + let s = -b - std::f64::sqrt(q); + + // There is a hit, and it is at a closer distance! So s becomes the new smaller dist. + if s > 0.01 && s < dist { + st = Hit; + dist = s; + bounce = p.add(&dir.scale(dist)).normalize(); + } + } + } + + (st, dist, bounce) +}