diff --git a/Cargo.lock b/Cargo.lock index 7d2c045..7ae4834 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,15 @@ # It is not intended for manual editing. version = 3 +[[package]] +name = "approx" +version = "0.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6" +dependencies = [ + "num-traits", +] + [[package]] name = "as-any" version = "0.3.1" @@ -20,6 +29,12 @@ version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +[[package]] +name = "bytemuck" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8334215b81e418a0a7bdb8ef0849474f40bb10c8b71f1c4ed315cff49f32494d" + [[package]] name = "cfg-if" version = "1.0.0" @@ -133,15 +148,53 @@ version = "0.1.0" dependencies = [ "as-any", "futures", + "nalgebra", "nix", ] +[[package]] +name = "matrixmultiply" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9380b911e3e96d10c1f415da0876389aaf1b56759054eeb0de7df940c456ba1a" +dependencies = [ + "autocfg", + "rawpointer", +] + [[package]] name = "memchr" version = "2.7.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" +[[package]] +name = "nalgebra" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26aecdf64b707efd1310e3544d709c5c0ac61c13756046aaaba41be5c4f66a3b" +dependencies = [ + "approx", + "matrixmultiply", + "nalgebra-macros", + "num-complex", + "num-rational", + "num-traits", + "simba", + "typenum", +] + +[[package]] +name = "nalgebra-macros" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "254a5372af8fc138e36684761d3c0cdb758a4410e938babcff1c860ce14ddbfc" +dependencies = [ + "proc-macro2", + "quote", + "syn", +] + [[package]] name = "nix" version = "0.29.0" @@ -154,6 +207,60 @@ dependencies = [ "libc", ] +[[package]] +name = "num-bigint" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a5e44f723f1133c9deac646763579fdb3ac745e418f2a7af9cd0c431da1f20b9" +dependencies = [ + "num-integer", + "num-traits", +] + +[[package]] +name = "num-complex" +version = "0.4.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "73f88a1307638156682bada9d7604135552957b7818057dcef22705b4d509495" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-integer" +version = "0.1.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7969661fd2958a5cb096e56c8e1ad0444ac2bbcd0061bd28660485a44879858f" +dependencies = [ + "num-traits", +] + +[[package]] +name = "num-rational" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f83d14da390562dca69fc84082e73e548e1ad308d24accdedd2720017cb37824" +dependencies = [ + "num-bigint", + "num-integer", + "num-traits", +] + +[[package]] +name = "num-traits" +version = "0.2.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" +dependencies = [ + "autocfg", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + [[package]] name = "pin-project-lite" version = "0.2.15" @@ -184,6 +291,34 @@ dependencies = [ "proc-macro2", ] +[[package]] +name = "rawpointer" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3" + +[[package]] +name = "safe_arch" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a" +dependencies = [ + "bytemuck", +] + +[[package]] +name = "simba" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3a386a501cd104797982c15ae17aafe8b9261315b5d07e3ec803f2ea26be0fa" +dependencies = [ + "approx", + "num-complex", + "num-traits", + "paste", + "wide", +] + [[package]] name = "slab" version = "0.4.9" @@ -204,8 +339,24 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "typenum" +version = "1.17.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825" + [[package]] name = "unicode-ident" version = "1.0.13" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b56cd4cadaeb79bbf1a5645f6b4f8dc5bde8834ad5894a8db35fda9efa1fe" + +[[package]] +name = "wide" +version = "0.7.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b828f995bf1e9622031f8009f8481a85406ce1f4d4588ff746d872043e855690" +dependencies = [ + "bytemuck", + "safe_arch", +] diff --git a/Cargo.toml b/Cargo.toml index c722130..b35b581 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,4 +21,5 @@ path = "src/bin/repl.rs" [dependencies] as-any = "0.3.1" futures = "0.3.30" +nalgebra = "0.33.2" nix = "0.29.0" diff --git a/src/bin/rt_demo.rs b/src/bin/rt_demo.rs new file mode 100644 index 0000000..a265547 --- /dev/null +++ b/src/bin/rt_demo.rs @@ -0,0 +1,5 @@ +use lispers::raytracer::{scene::Scene, types::Light}; + +fn main() { + let scene = Scene::new(); +} diff --git a/src/lib.rs b/src/lib.rs index e1be52d..65557c7 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod lisp; pub mod parser; +pub mod raytracer; diff --git a/src/raytracer/camera.rs b/src/raytracer/camera.rs new file mode 100644 index 0000000..966006d --- /dev/null +++ b/src/raytracer/camera.rs @@ -0,0 +1,70 @@ +use super::{ + scene::Scene, + types::{Point3, Ray, Scalar, Vector3}, +}; +// use image::Rgb32FImage; + +pub struct Camera { + position: Point3, + up: Vector3, + right: Vector3, + upper_left: Point3, + x_dir: Vector3, + y_dir: Vector3, + width: usize, + height: usize, +} + +impl Camera { + pub fn new( + position: Point3, + direction: Vector3, + up: Vector3, + fovy: Scalar, + width: usize, + height: usize, + ) -> Camera { + let aspect_ratio = width as Scalar / height as Scalar; + let fovx = fovy * aspect_ratio; + let right = direction.cross(&up).normalize(); + let x_dir = right * (fovx / 2.0).tan(); + let y_dir = -up * (fovy / 2.0).tan(); + let upper_left = position + direction - x_dir + y_dir; + + Camera { + position, + up, + right, + upper_left, + x_dir, + y_dir, + width, + height, + } + } +} + +impl Camera { + pub fn ray_at_relative(&self, x: Scalar, y: Scalar) -> Ray { + let x_dir = self.x_dir * x; + let y_dir = self.y_dir * y; + Ray::new( + self.position, + (self.upper_left + x_dir - y_dir - self.position).normalize(), + ) + } + + pub fn ray_at(&self, x: usize, y: usize) -> Ray { + let x = x as Scalar / self.width as Scalar; + let y = y as Scalar / self.height as Scalar; + self.ray_at_relative(x, y) + } + + // pub fn render(&self, scene: &Scene, depth: u32) -> Rgb32FImage { + // Rgb32FImage::from_fn(self.width, self.height, |x, y| { + // let ray = self.ray_at(x, y); + // let color = scene.trace(&ray, depth); + // color.into() + // }) + // } +} diff --git a/src/raytracer/mod.rs b/src/raytracer/mod.rs new file mode 100644 index 0000000..4851590 --- /dev/null +++ b/src/raytracer/mod.rs @@ -0,0 +1,5 @@ +pub mod camera; +pub mod scene; +pub mod sphere; +pub mod types; +pub mod vec; diff --git a/src/raytracer/scene.rs b/src/raytracer/scene.rs new file mode 100644 index 0000000..3046a16 --- /dev/null +++ b/src/raytracer/scene.rs @@ -0,0 +1,89 @@ +use super::types::Color; +use super::types::Intersect; +use super::types::Light; +use super::types::Material; +use super::types::Point3; +use super::types::Ray; +use super::types::Scalar; +use super::types::Vector3; +use super::vec::reflect; +extern crate nalgebra as na; + +pub struct Scene { + objects: Vec>, + lights: Vec, +} + +impl Scene { + pub fn new() -> Scene { + Scene { + objects: Vec::new(), + lights: Vec::new(), + } + } + + pub fn add_object(&mut self, obj: Box) { + self.objects.push(obj); + } + + pub fn add_light(&mut self, light: Light) { + self.lights.push(light); + } + + pub fn trace(&self, ray: &Ray, depth: u32) -> Color { + if depth == 0 { + return na::Vector3::new(0.0, 0.0, 0.0); + } + + match self + .objects + .iter() + .filter_map(|obj| obj.intersect(ray)) + .min_by(|(_, _, t1, _), (_, _, t2, _)| t1.partial_cmp(t2).unwrap()) + { + None => { + return na::Vector3::new(0.0, 0.0, 0.0); + } + Some((isect_pt, isect_norm, isect_dist, material)) => { + // Lighting of material at the intersection point + let color = + self.lighting(-&ray.direction, material, isect_pt, isect_norm, isect_dist); + + // Calculate reflections, if the material has mirror properties + if material.mirror > 0.0 { + let new_ray = Ray { + origin: isect_pt, + direction: reflect(ray.direction, isect_norm), + }; + return (1.0 - material.mirror) * color + + material.mirror * self.trace(&new_ray, depth - 1); + } else { + return color; + } + } + } + } + + fn lighting( + &self, + view: Vector3, + material: &Material, + isect_pt: Point3, + isect_norm: Vector3, + isect_dist: Scalar, + ) -> Color { + let mut color: Color = na::Vector3::new(0.0, 0.0, 0.0); + + for light in &self.lights { + let l = (isect_pt - light.position).normalize(); + let cos_theta = l.dot(&isect_norm); + + if cos_theta > 0.0 { + // Diffuse + color += material.diffuse_color.component_mul(&light.color) * cos_theta; + } + } + + color + } +} diff --git a/src/raytracer/sphere.rs b/src/raytracer/sphere.rs new file mode 100644 index 0000000..707a9f0 --- /dev/null +++ b/src/raytracer/sphere.rs @@ -0,0 +1,49 @@ +use super::types::{Intersect, Material, Point3, Ray, Scalar, Vector3}; + +extern crate nalgebra as na; + +pub struct Sphere { + center: Point3, + radius: Scalar, + material: Material, +} + +/// Numerical error tolerance +const EPSILON: Scalar = 1e-5; + +impl Intersect for Sphere { + fn intersect<'a>(&'a self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, &'a Material)> { + let co = ray.origin - self.center; + + let a = ray.direction.dot(&ray.direction); + let b = 2.0 * ray.direction.dot(&co); + let c = co.dot(&co) - (self.radius * self.radius); + let d = b * b - 4.0 * a * c; + + if d >= 0.0 { + let e = d.sqrt(); + let t1 = (-b - e) / (2.0 * a); + let t2 = (-b + e) / (2.0 * a); + let mut t = Scalar::MAX; + + if t1 > EPSILON && t1 < t { + t = t1; + } + if t2 > EPSILON && t2 < t { + t = t2; + } + + if t < Scalar::MAX { + let isect_pt: Point3 = ray.origin + ray.direction * t; + return Some(( + isect_pt, + (isect_pt - self.center) / self.radius, + t, + &self.material, + )); + } + } + + None + } +} diff --git a/src/raytracer/types.rs b/src/raytracer/types.rs new file mode 100644 index 0000000..b76da96 --- /dev/null +++ b/src/raytracer/types.rs @@ -0,0 +1,58 @@ +extern crate nalgebra as na; + +pub type Scalar = f32; +pub type Vector3 = na::Vector3; +pub type Point3 = na::Point3; +pub type Color = Vector3; + +pub trait Intersect { + fn intersect<'a>(&'a self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, &'a Material)>; +} + +pub struct Light { + pub position: Point3, + pub color: Color, +} + +impl Light { + pub fn new(position: Point3, color: Color) -> Light { + Light { position, color } + } +} + +pub struct Ray { + pub origin: Point3, + pub direction: Vector3, +} + +impl Ray { + pub fn new(origin: Point3, direction: Vector3) -> Ray { + Ray { origin, direction } + } +} + +pub struct Material { + pub ambient_color: Color, + pub diffuse_color: Color, + pub specular_color: Color, + pub shinyness: Scalar, + pub mirror: Scalar, +} + +impl Material { + pub fn new( + ambient_color: Color, + diffuse_color: Color, + specular_color: Color, + shinyness: Scalar, + mirror: Scalar, + ) -> Material { + Material { + ambient_color, + diffuse_color, + specular_color, + shinyness, + mirror, + } + } +} diff --git a/src/raytracer/vec.rs b/src/raytracer/vec.rs new file mode 100644 index 0000000..c831219 --- /dev/null +++ b/src/raytracer/vec.rs @@ -0,0 +1,14 @@ +use super::types::Vector3; + +extern crate nalgebra as na; + +pub fn reflect(v: Vector3, n: Vector3) -> Vector3 { + v - 2.0 * v.dot(&n) * n +} + +pub fn rotate(v: &Vector3, axis: &Vector3, angle: f32) -> Vector3 { + //let axis = na::Unit::new_normalize(axis); + //let rot = na::Rotation3::from_axis_angle(&axis, angle); + //(rot * v) + todo!() +}