diff --git a/.envrc b/.envrc deleted file mode 100644 index b9238c3..0000000 --- a/.envrc +++ /dev/null @@ -1,3 +0,0 @@ -#!/usr/bin/env bash - -use flake diff --git a/.gitignore b/.gitignore index 4729dc0..914880f 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ +# Generated by Cargo +# will have compiled files and executables debug/ target/ -.direnv/ # These are backup files generated by rustfmt **/*.rs.bk diff --git a/Cargo.lock b/Cargo.lock index 38aaf58..add5afc 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -40,9 +40,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693" [[package]] name = "cfg-if" -version = "1.0.0" +version = "0.1.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" [[package]] name = "cloudabi" @@ -61,18 +61,18 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba" [[package]] name = "generic-array" -version = "0.12.4" +version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" dependencies = [ "typenum", ] [[package]] name = "getrandom" -version = "0.1.16" +version = "0.1.15" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" +checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6" dependencies = [ "cfg-if", "libc", @@ -81,9 +81,9 @@ dependencies = [ [[package]] name = "libc" -version = "0.2.94" +version = "0.2.80" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" +checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614" [[package]] name = "libm" @@ -93,9 +93,9 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a" [[package]] name = "matrixmultiply" -version = "0.2.4" +version = "0.2.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" +checksum = "d4f7ec66360130972f34830bfad9ef05c6610a43938a467bcc9ab9369ab3478f" dependencies = [ "rawpointer", ] @@ -336,9 +336,9 @@ dependencies = [ [[package]] name = "typenum" -version = "1.13.0" +version = "1.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" +checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33" [[package]] name = "wasi" diff --git a/Cargo.toml b/Cargo.toml index 2967baf..ed2abd2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,9 +1,11 @@ [package] name = "render" version = "0.1.0" -authors = ["Kiana Sheibani"] +authors = ["Bijan Sheibani"] edition = "2018" +# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html + [dependencies] nalgebra = "0.18" rand = "0.7.3" diff --git a/LICENSE b/LICENSE deleted file mode 100644 index 712386e..0000000 --- a/LICENSE +++ /dev/null @@ -1,21 +0,0 @@ -MIT License - -Copyright (c) 2024 Kiana Sheibani - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. diff --git a/README.md b/README.md index b4409c4..9e985ae 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,4 @@ # rust-render - A raytracing 3D renderer written in Rust. ### Checklist of features diff --git a/flake.lock b/flake.lock deleted file mode 100644 index cfec747..0000000 --- a/flake.lock +++ /dev/null @@ -1,285 +0,0 @@ -{ - "nodes": { - "crane": { - "flake": false, - "locked": { - "lastModified": 1727316705, - "narHash": "sha256-/mumx8AQ5xFuCJqxCIOFCHTVlxHkMT21idpbgbm/TIE=", - "owner": "ipetkov", - "repo": "crane", - "rev": "5b03654ce046b5167e7b0bccbd8244cb56c16f0e", - "type": "github" - }, - "original": { - "owner": "ipetkov", - "ref": "v0.19.0", - "repo": "crane", - "type": "github" - } - }, - "dream2nix": { - "inputs": { - "nixpkgs": [ - "nci", - "nixpkgs" - ], - "purescript-overlay": "purescript-overlay", - "pyproject-nix": "pyproject-nix" - }, - "locked": { - "lastModified": 1728585693, - "narHash": "sha256-rhx5SYpIkPu7d+rjF9FGGBVxS0BwAEkmYIsJg2a3E20=", - "owner": "nix-community", - "repo": "dream2nix", - "rev": "c6935471f7e1a9e190aaa9ac9823dca34e00d92a", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "dream2nix", - "type": "github" - } - }, - "flake-compat": { - "flake": false, - "locked": { - "lastModified": 1696426674, - "narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=", - "owner": "edolstra", - "repo": "flake-compat", - "rev": "0f9255e01c2351cc7d116c072cb317785dd33b33", - "type": "github" - }, - "original": { - "owner": "edolstra", - "repo": "flake-compat", - "type": "github" - } - }, - "flake-parts": { - "inputs": { - "nixpkgs-lib": [ - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1727826117, - "narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "mk-naked-shell": { - "flake": false, - "locked": { - "lastModified": 1681286841, - "narHash": "sha256-3XlJrwlR0nBiREnuogoa5i1b4+w/XPe0z8bbrJASw0g=", - "owner": "yusdacra", - "repo": "mk-naked-shell", - "rev": "7612f828dd6f22b7fb332cc69440e839d7ffe6bd", - "type": "github" - }, - "original": { - "owner": "yusdacra", - "repo": "mk-naked-shell", - "type": "github" - } - }, - "nci": { - "inputs": { - "crane": "crane", - "dream2nix": "dream2nix", - "mk-naked-shell": "mk-naked-shell", - "nixpkgs": [ - "nixpkgs" - ], - "parts": "parts", - "rust-overlay": "rust-overlay", - "treefmt": "treefmt" - }, - "locked": { - "lastModified": 1728886586, - "narHash": "sha256-pg8O8Vborbo0dcNuE4K5PCVW6MXSJ7LEv9toAnSJ7nw=", - "owner": "yusdacra", - "repo": "nix-cargo-integration", - "rev": "b80fe61d4a7c7a7dd9f3d32c3e19aef2baae0bfa", - "type": "github" - }, - "original": { - "owner": "yusdacra", - "repo": "nix-cargo-integration", - "type": "github" - } - }, - "nixpkgs": { - "locked": { - "lastModified": 1728538411, - "narHash": "sha256-f0SBJz1eZ2yOuKUr5CA9BHULGXVSn6miBuUWdTyhUhU=", - "owner": "NixOS", - "repo": "nixpkgs", - "rev": "b69de56fac8c2b6f8fd27f2eca01dcda8e0a4221", - "type": "github" - }, - "original": { - "owner": "NixOS", - "ref": "nixpkgs-unstable", - "repo": "nixpkgs", - "type": "github" - } - }, - "parts": { - "inputs": { - "nixpkgs-lib": [ - "nci", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1727826117, - "narHash": "sha256-K5ZLCyfO/Zj9mPFldf3iwS6oZStJcU4tSpiXTMYaaL0=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "3d04084d54bedc3d6b8b736c70ef449225c361b1", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "purescript-overlay": { - "inputs": { - "flake-compat": "flake-compat", - "nixpkgs": [ - "nci", - "dream2nix", - "nixpkgs" - ], - "slimlock": "slimlock" - }, - "locked": { - "lastModified": 1724504251, - "narHash": "sha256-TIw+sac0NX0FeAneud+sQZT+ql1G/WEb7/Vb436rUXM=", - "owner": "thomashoneyman", - "repo": "purescript-overlay", - "rev": "988b09676c2a0e6a46dfa3589aa6763c90476b8a", - "type": "github" - }, - "original": { - "owner": "thomashoneyman", - "repo": "purescript-overlay", - "type": "github" - } - }, - "pyproject-nix": { - "flake": false, - "locked": { - "lastModified": 1702448246, - "narHash": "sha256-hFg5s/hoJFv7tDpiGvEvXP0UfFvFEDgTdyHIjDVHu1I=", - "owner": "davhau", - "repo": "pyproject.nix", - "rev": "5a06a2697b228c04dd2f35659b4b659ca74f7aeb", - "type": "github" - }, - "original": { - "owner": "davhau", - "ref": "dream2nix", - "repo": "pyproject.nix", - "type": "github" - } - }, - "root": { - "inputs": { - "flake-parts": "flake-parts", - "nci": "nci", - "nixpkgs": "nixpkgs", - "systems": "systems" - } - }, - "rust-overlay": { - "flake": false, - "locked": { - "lastModified": 1728873041, - "narHash": "sha256-e4jz7yFADiZjMhv+iQwYtAN8AOUlOpbNQYnbwUFLjeM=", - "owner": "oxalica", - "repo": "rust-overlay", - "rev": "bdbe1611c2029de90bca372ce0b1e3b4fa65f55a", - "type": "github" - }, - "original": { - "owner": "oxalica", - "repo": "rust-overlay", - "type": "github" - } - }, - "slimlock": { - "inputs": { - "nixpkgs": [ - "nci", - "dream2nix", - "purescript-overlay", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1688756706, - "narHash": "sha256-xzkkMv3neJJJ89zo3o2ojp7nFeaZc2G0fYwNXNJRFlo=", - "owner": "thomashoneyman", - "repo": "slimlock", - "rev": "cf72723f59e2340d24881fd7bf61cb113b4c407c", - "type": "github" - }, - "original": { - "owner": "thomashoneyman", - "repo": "slimlock", - "type": "github" - } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } - }, - "treefmt": { - "inputs": { - "nixpkgs": [ - "nci", - "nixpkgs" - ] - }, - "locked": { - "lastModified": 1727984844, - "narHash": "sha256-xpRqITAoD8rHlXQafYZOLvUXCF6cnZkPfoq67ThN0Hc=", - "owner": "numtide", - "repo": "treefmt-nix", - "rev": "4446c7a6fc0775df028c5a3f6727945ba8400e64", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "treefmt-nix", - "type": "github" - } - } - }, - "root": "root", - "version": 7 -} diff --git a/flake.nix b/flake.nix deleted file mode 100644 index bd7d11a..0000000 --- a/flake.nix +++ /dev/null @@ -1,20 +0,0 @@ -{ - description = "Raytracing renderer written in Rust"; - - inputs = { - nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - systems.url = "github:nix-systems/default"; - - flake-parts.url = "github:hercules-ci/flake-parts"; - flake-parts.inputs.nixpkgs-lib.follows = "nixpkgs"; - - nci.url = "github:yusdacra/nix-cargo-integration"; - nci.inputs.nixpkgs.follows = "nixpkgs"; - }; - - outputs = inputs@{ flake-parts, systems, nci, ... }: - flake-parts.lib.mkFlake { inherit inputs; } { - systems = import systems; - imports = [ nci.flakeModule ./module.nix ]; - }; -} diff --git a/module.nix b/module.nix deleted file mode 100644 index 3aea4eb..0000000 --- a/module.nix +++ /dev/null @@ -1,18 +0,0 @@ -{ - perSystem = { pkgs, config, self', ... }: - let - cfg = config.nci.outputs.render; - in { - nci.toolchainConfig = ./rust-toolchain.toml; - - nci.projects.render.path = ./.; - - # Exports - checks.build = self'.packages.render; - packages.default = self'.packages.render; - packages.render = cfg.packages.release; - devShells.default = cfg.devShell.overrideAttrs (prev: { - buildInputs = prev.buildInputs ++ [ pkgs.rust-analyzer ]; - }); - }; -} diff --git a/rust-toolchain.toml b/rust-toolchain.toml deleted file mode 100644 index c9025df..0000000 --- a/rust-toolchain.toml +++ /dev/null @@ -1,2 +0,0 @@ -[toolchain] -channel = "nightly-2024-10-01" diff --git a/src/camera.rs b/src/camera.rs index 1e14362..c269cc0 100644 --- a/src/camera.rs +++ b/src/camera.rs @@ -1,121 +1,87 @@ -use nalgebra::geometry::{Point2, Point3}; -use nalgebra::*; +extern crate nalgebra as na; -use crate::util::Ray; +use na::*; +use na::geometry::{Point2, Point3}; + +use crate::types::Ray; #[derive(Debug)] pub struct Camera { - matrix: Isometry3, // The transformation that stores the - // position and orientation of the camera. (Not actually a matrix, but w/e) - focal_length: f64, // The distance from the camera origin to the canvas. - canvas_size: Vector2, // The size of the canvas within the world space. + matrix: Isometry3, // The transformation that stores the + // position and orientation of the camera. (Not actually a matrix, but w/e) - pub image_size: Vector2, // The size of the final image in pixels. + focal_length: f32, // The distance from the camera origin to the canvas. + canvas_size: Vector2, // The size of the canvas within the world space. + + pub image_size: Vector2 // The size of the final image in pixels. } impl Camera { + // Constructs a new camera from a position and viewing direction. - pub fn new_( - pos: Point3, - dir: Vector3, - up: Vector3, - focal_length: f64, - aspect_ratio: f64, - canvas_y: f64, - image_y: u64, - ) -> Self { + pub fn new_(pos: Point3, dir: Vector3, up: Vector3, + focal_length: f32, aspect_ratio: f32, canvas_y: f32, image_y: u32) -> Self { let iso = Isometry3::face_towards(&pos, &(pos + dir), &up); Camera { matrix: iso, focal_length: focal_length, - canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y), - image_size: Vector2::new((image_y as f64 * aspect_ratio) as u64, image_y), + canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y), + image_size: Vector2::new((image_y as f32 * aspect_ratio) as u32, image_y) } } // Constructs a new camera from a position and viewing direction // (assuming the camera is oriented upright). - pub fn new( - pos: Point3, - dir: Vector3, - focal_length: f64, - aspect_ratio: f64, - canvas_y: f64, - image_y: u64, - ) -> Self { - Camera::new_( - pos, - dir, - Vector3::y(), - focal_length, - aspect_ratio, - canvas_y, - image_y, - ) - } + pub fn new(pos: Point3, dir: Vector3, + focal_length: f32, aspect_ratio: f32, canvas_y: f32, image_y: u32) -> Self + { Camera::new_(pos, dir, Vector3::y(), focal_length, aspect_ratio, canvas_y, image_y) } - pub fn pos(&self) -> Point3 { - Point3::from(self.matrix.translation.vector) - } + pub fn pos(&self) -> Point3 { Point3::from(self.matrix.translation.vector) } // Takes a 2D point in the image space and // maps it to the 3D point on the canvas. - fn project(&self, x: u64, y: u64) -> Point3 { + fn project(&self, x: u32, y: u32) -> Point3 { // convert point from raster coordinates to center-based coordinates - let pixelndc = Point2::new( - x as f64 + 0.5 - self.image_size.x as f64 * 0.5, - -(y as f64 + 0.5) + self.image_size.y as f64 * 0.5, - ); + let pixelndc = Point2::new(x as f32 + 0.5 - self.image_size.x as f32 * 0.5, -(y as f32 + 0.5) + self.image_size.y as f32 * 0.5); - let point: Point3 = Point::from( - pixelndc - .coords - .component_div(&self.image_size.map(|x| x as f64)) - .component_mul(&self.canvas_size) - .fixed_resize(self.focal_length), - ); + let point: Point3 = Point::from(pixelndc.coords.component_div(&self.image_size.map(|x| x as f32)) + .component_mul(&self.canvas_size) + .fixed_resize(self.focal_length)); self.matrix * point } // Takes a 2D point in the image space and // returns a ray in the world space, for use in raytracing. - pub fn raycast(&self, x: u64, y: u64) -> Ray { + pub fn raycast(&self, x: u32, y: u32) -> Ray { Ray::from_points(self.pos(), self.project(x, y)) } } + #[cfg(test)] mod tests { use super::*; - fn round(point: Point3) -> Point3 { + fn round(point: Point3) -> Point3 { Point::from(point.coords.map(|x| x.round())) } #[test] fn camera_pos() { - let camera: Camera = Camera::new( - Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, - 1.0, - 2.0, - 800, - ); + let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, 1.0, + 2.0, 800); assert_eq!(camera.pos(), Point3::new(-5.0, 0.0, 0.0)); } #[test] fn camera_matrix1() { - let camera: Camera = Camera::new( - Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, - 1.0, - 2.0, - 800, - ); + let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, 1.0, + 2.0, 800); let point = Point3::new(0.0, 0.0, 4.0); let point = camera.matrix * point; @@ -125,14 +91,10 @@ mod tests { #[test] fn camera_matrix2() { - let camera: Camera = Camera::new( - Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, - 1.0, - 2.0, - 800, - ); + let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, 1.0, + 2.0, 800); let point = Point3::new(4.0, 0.0, 0.0); let point = camera.matrix * point; @@ -142,14 +104,10 @@ mod tests { #[test] fn camera_project1() { - let camera: Camera = Camera::new( - Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, - 1.0, - 2.0, - 800, - ); + let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, 1.0, + 2.0, 800); let point = camera.project(400, 400); let point = round(point); // round to avoid errors @@ -158,14 +116,10 @@ mod tests { #[test] fn camera_project2() { - let camera: Camera = Camera::new( - Point3::new(-5.0, 0.0, 0.0), - Vector3::new(1.0, 0.0, 0.0), - 1.0, - 1.0, - 2.0, - 800, - ); + let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0), + Vector3::new(1.0, 0.0, 0.0), + 1.0, 1.0, + 2.0, 800); let point = camera.project(0, 0); let point = round(point); // round to avoid errors diff --git a/src/main.rs b/src/main.rs index 839ee33..30f9fb8 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,20 +1,18 @@ +extern crate nalgebra as na; + +use std::time::Instant; use std::fs::File; use std::io::Write; -use std::time::Instant; -use nalgebra::*; +use na::*; -mod camera; -use camera::*; -mod util; -use util::*; -mod object; -use object::*; -mod render; -use render::*; +mod camera; use camera::*; +mod types; use types::*; +mod object; use object::*; +mod render; use render::*; fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()> { - let width = camera.image_size.x; + let width = camera.image_size.x; let height = camera.image_size.y; let mut buffer: Vec = Vec::with_capacity((width * height) as usize); @@ -37,22 +35,18 @@ fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()> } fn main() -> std::io::Result<()> { - let camera = Camera::new( - Point3::new(0.0, 5.0, 0.0), - Vector3::new(0.0, -1.0, 0.0), - 1.0, - 16.0 / 9.0, - 2.0, - 720, - ); + + let camera = Camera::new(Point3::new(0.0,5.0,0.0), Vector3::new(0.0,-1.0,0.0), 1.0, 16.0 / 9.0, 2.0, 720); let scene = Scene { - objects: vec![Object::new(Plane::xz(|_, _| Texture { - color: Color::white(), - albedo: 0.8, - }))], - lights: vec![], - background: Color::gray(0.5), + objects: vec![ + Object::new(Plane::xz(|_, _| Texture { color: Color::white(), albedo: 0.8 })), + + ], + lights: vec![ + + ], + background: Color::gray(0.5) }; let before = Instant::now(); diff --git a/src/object.rs b/src/object.rs index 65b9bec..d7d4d8b 100644 --- a/src/object.rs +++ b/src/object.rs @@ -1,22 +1,19 @@ -mod sphere; -pub use sphere::*; -mod plane; -pub use plane::*; -mod triangle; -pub use triangle::*; -mod bound; -pub use bound::*; -mod point_light; -pub use point_light::*; -use crate::util::*; +mod sphere; pub use sphere::*; +mod plane; pub use plane::*; +mod triangle; pub use triangle::*; +mod bound; pub use bound::*; +mod point_light; pub use point_light::*; + +use crate::types::*; // A trait for types that can be in Objects. pub trait Surface { + // Takes in a ray and performs an intersection test // on itself. If the ray intersects the object, // returns the distance to the intersection point. - fn intersect(&self, ray: Ray) -> Option; + fn intersect(&self, ray: Ray) -> Option; // Takes in a point (assumed to be on the object's surface) // and returns the normal vector off of that point. @@ -27,12 +24,12 @@ pub trait Surface { fn get_texture(&self, point: Point3f) -> Texture; // Creates a bounding sphere around the object. - fn bound(&self) -> Option; + fn bound(&self) -> Bound; } pub struct Object { pub surface: Box, - bound: Option, + bound: Bound } impl Object { @@ -41,23 +38,18 @@ impl Object { let bound = surface.bound(); Object { surface: Box::new(surface), - bound, + bound } } - pub fn intersect(&self, ray: Ray) -> Option { - if !self.bound.as_ref().is_some_and(|b| !b.is_intersected(ray)) { + + pub fn intersect(&self, ray: Ray) -> Option { + if self.bound.is_intersected(ray) { self.surface.intersect(ray) - } else { - None - } - } - pub fn normal(&self, point: Point3f) -> Unit3f { - self.surface.normal(point) - } - pub fn get_texture(&self, point: Point3f) -> Texture { - self.surface.get_texture(point) + } else { None } } + pub fn normal(&self, point: Point3f) -> Unit3f { self.surface.normal(point) } + pub fn get_texture(&self, point: Point3f) -> Texture { self.surface.get_texture(point) } } pub trait Light { @@ -68,7 +60,7 @@ pub trait Light { fn get_color(&self, point: Point3f) -> Color; // Compute intensity on a point. - fn intensity(&self, point: Point3f) -> f64; + fn intensity(&self, point: Point3f) -> f32; // Return the direction from the point to the light source. fn direction(&self, point: Point3f) -> Unit3f; @@ -77,5 +69,5 @@ pub trait Light { pub struct Scene { pub objects: Vec, pub lights: Vec>, - pub background: Color, + pub background: Color } diff --git a/src/object/bound.rs b/src/object/bound.rs index b461277..49c0246 100644 --- a/src/object/bound.rs +++ b/src/object/bound.rs @@ -1,21 +1,30 @@ -// use na::distance; -use nalgebra::geometry::Point3; +extern crate nalgebra as na; -use crate::util::*; +// use na::distance; +use na::geometry::Point3; + +use crate::types::*; // A bounding sphere, used for // intersection test optimization. #[derive(Debug)] pub struct Bound { pub center: Point3f, - pub radius: f64, + pub radius: f32, + + // If true, then the bounding sphere is disabled. + pub bypass: bool } impl Bound { pub fn is_intersected(&self, ray: Ray) -> bool { + if self.bypass { return true; } + let l = ray.origin - self.center; l.norm_squared() >= self.radius * self.radius } // pub fn contains(&self, point: &Point3f) -> bool { distance(&self.center, point) < self.radius } + + pub fn bypass() -> Self { Bound { center: Point3::origin(), radius: 0.0, bypass: true } } } diff --git a/src/object/plane.rs b/src/object/plane.rs index 213e555..981fb5f 100644 --- a/src/object/plane.rs +++ b/src/object/plane.rs @@ -1,105 +1,81 @@ -use nalgebra::geometry::Point3; -use nalgebra::*; +extern crate nalgebra as na; -use super::{bound::*, Surface}; -use crate::util::*; +use na::*; +use na::geometry::Point3; + +use crate::types::*; +use super::{Surface, bound::*}; pub struct Plane { - pub center: Point3f, // Plane origin (used for texture mapping). - pub normal: Unit3f, // Precomputed plane normal. + pub center: Point3f, // Plane origin (used for texture mapping). + pub normal: Unit3f, // Precomputed plane normal. x_axis: Vector3f, // Plane x-axis (The 3D direction that corresponds to the x-direction on the plane). y_axis: Vector3f, // Plane y-axis (The 3D direction that corresponds to the y-direction on the plane). - texture: Box Texture>, // Texture map. - // Input coordinates are defined in terms of the axes above. + texture: Box Texture> // Texture map. + // Input coordinates are defined in terms of the axes above. } #[allow(dead_code)] impl Plane { // Creates a new plane. pub fn new(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self - where - F: Fn(f64, f64) -> Texture, + where F: Fn(f32, f32) -> Texture { Plane { center, normal: Unit::new_normalize(x_axis.cross(&y_axis)), x_axis: x_axis, y_axis: y_axis, - texture: Box::new(texture), + texture: Box::new(texture) } } // Creates a new plane with the normal flipped. - pub fn new_flip( - center: Point3f, - x_axis: Vector3f, - y_axis: Vector3f, - texture: F, - ) -> Self - where - F: Fn(f64, f64) -> Texture, + pub fn new_flip(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self + where F: Fn(f32, f32) -> Texture { Plane { center: center, normal: Unit::new_normalize(y_axis.cross(&x_axis)), x_axis: x_axis, y_axis: y_axis, - texture: Box::new(texture), + texture: Box::new(texture) } } // Creates a new plane of a solid color. - pub fn new_solid( - center: Point3f, - x_axis: Vector3f, - y_axis: Vector3f, - texture: Texture, - ) -> Self { - Plane::new(center, x_axis, y_axis, move |_, _| texture) - } + pub fn new_solid(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self + { Plane::new(center, x_axis, y_axis, move |_, _| texture) } // Creates a new flipped plane of a solid color. - pub fn new_solid_flip( - center: Point3f, - x_axis: Vector3f, - y_axis: Vector3f, - texture: Texture, - ) -> Self { - Plane::new_flip(center, x_axis, y_axis, move |_, _| texture) - } + pub fn new_solid_flip(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self + { Plane::new_flip(center, x_axis, y_axis, move |_, _| texture) } + // Creates a new XY-plane with the given texture map. - pub fn xy(texture: impl 'static + Fn(f64, f64) -> Texture) -> Self { - Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) - } + pub fn xy(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self + { Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) } // Creates a new XZ-plane with the given texture map. - pub fn xz(texture: impl 'static + Fn(f64, f64) -> Texture) -> Self { - Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) - } + pub fn xz(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self + { Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) } } impl Surface for Plane { - fn intersect(&self, ray: Ray) -> Option { + fn intersect(&self, ray: Ray) -> Option { + let d = self.normal.dot(&ray.direction); - if d > -1e-3 { - return None; - } + if d < 1e-3 { return None; } let t = (self.center - ray.origin).dot(&*self.normal) / d; - if t >= 0.0 { - Some(t) - } else { - None - } + if t >= 0.0 { Some(t) } + else { None } } - fn normal(&self, _point: Point3f) -> Unit3f { - self.normal - } + fn normal(&self, _point: Point3f) -> Unit3f { self.normal } fn get_texture(&self, point: Point3f) -> Texture { let rel_pos = point - self.center; @@ -111,8 +87,7 @@ impl Surface for Plane { (*self.texture)(x, y) } - // Planes are infinite, so no finite bounding sphere could possibly contain one - fn bound(&self) -> Option { - None - } + // Planes are infinite, so no finite + // bounding sphere could possibly contain one. + fn bound(&self) -> Bound { Bound::bypass() } } diff --git a/src/object/point_light.rs b/src/object/point_light.rs index 2476d22..3276356 100644 --- a/src/object/point_light.rs +++ b/src/object/point_light.rs @@ -1,41 +1,34 @@ -use nalgebra::*; +extern crate nalgebra as na; +use na::*; + +use crate::types::*; use super::*; -use crate::util::*; pub struct PointLight { pub pos: Point3f, pub color: Color, - pub intensity: f64, + pub intensity: f32 } #[allow(dead_code)] impl PointLight { - pub fn new(pos: Point3f, color: Color, intensity: f64) -> PointLight { - PointLight { - pos, - color, - intensity, - } + pub fn new(pos: Point3f, color: Color, intensity: f32) -> PointLight { + PointLight { pos, color, intensity } } } impl Light for PointLight { fn check_shadow(&self, point: Point3f, objects: &Vec) -> bool { let max_d = distance(&self.pos, &point); - objects - .iter() - .filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point))) - .all(|d| d - max_d > -1e-3) + objects.iter() + .filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point))) + .all(|d| d - max_d > -1e-3 ) } - fn get_color(&self, _point: Point3f) -> Color { - self.color - } + fn get_color(&self, _point: Point3f) -> Color { self.color } - fn intensity(&self, _point: Point3f) -> f64 { - self.intensity - } + fn intensity(&self, _point: Point3f) -> f32 { self.intensity } fn direction(&self, point: Point3f) -> Unit3f { Unit::new_normalize(self.pos - point) @@ -49,13 +42,7 @@ mod tests { #[test] fn point_light_check_shadow() { let light = PointLight::new(Point3::new(0.0, 1.0, 0.0), Color::white(), 1.0); - let block = Object::new(Sphere::new_solid( - 0.0, - 0.5, - 0.0, - 0.1, - Texture::new(0.0, 0.0, 0.0, 0.0), - )); + let block = Object::new(Sphere::new_solid(0.0, 0.5, 0.0, 0.1, Texture::new(0.0, 0.0, 0.0, 0.0))); assert!(light.check_shadow(Point3::origin(), &Vec::new())); assert!(!light.check_shadow(Point3::origin(), &vec![block])); diff --git a/src/object/sphere.rs b/src/object/sphere.rs index 6693e0a..232637f 100644 --- a/src/object/sphere.rs +++ b/src/object/sphere.rs @@ -1,55 +1,49 @@ -use std::f64::consts::PI; +extern crate nalgebra as na; -use nalgebra::geometry::Point3; -use nalgebra::*; +use std::f32::consts::PI; -use super::{bound::*, Surface}; -use crate::util::*; +use na::*; +use na::geometry::Point3; + +use crate::types::*; +use super::{Surface, bound::*}; pub struct Sphere { pub center: Point3f, // Center point of the sphere. - pub radius: f64, // Radius of the sphere. + pub radius: f32, // Radius of the sphere. - texture: Box Texture>, // Texture map. - // Uses spherical coordinates (normalized from 0-1) as input. + texture: Box Texture> // Texture map. + // Uses spherical coordinates (normalized from 0-1) as input. } #[allow(dead_code)] impl Sphere { // Creates a new sphere. - pub fn new(x: f64, y: f64, z: f64, radius: f64, texture: F) -> Self - where - F: Fn(f64, f64) -> Texture, + pub fn new(x: f32, y: f32, z: f32, radius: f32, texture: F) -> Self + where F: Fn(f32, f32) -> Texture { Sphere { - center: Point3::new(x, y, z), - radius, - texture: Box::new(texture), + center: Point3::new(x, y, z), radius, + texture: Box::new(texture) } } // Creates a new sphere of a solid color. - pub fn new_solid(x: f64, y: f64, z: f64, radius: f64, texture: Texture) -> Self { - Sphere::new(x, y, z, radius, move |_, _| texture) - } + pub fn new_solid(x: f32, y: f32, z: f32, radius: f32, texture: Texture) -> Self + { Sphere::new(x, y, z, radius, move |_, _| texture) } } impl Surface for Sphere { - fn intersect(&self, ray: Ray) -> Option { - fn solve_quadratic(b: f64, c: f64) -> Option<(f64, f64)> { + fn intersect(&self, ray: Ray) -> Option { + fn solve_quadratic(b: f32, c: f32) -> Option<(f32, f32)> { let discr = b * b - 4.0 * c; - if discr < 0.0 { - None - } else if discr == 0.0 { + if discr < 0.0 { None } + else if discr == 0.0 { let x = -0.5 * b; Some((x, x)) } else { - let q = if b > 0.0 { - -0.5 * (b + discr.sqrt()) - } else { - -0.5 * (b - discr.sqrt()) - }; + let q = if b > 0.0 { -0.5 * (b + discr.sqrt()) } else { -0.5 * (b - discr.sqrt()) }; Some((q, c / q)) } } @@ -60,17 +54,11 @@ impl Surface for Sphere { let (mut t0, mut t1) = solve_quadratic(b, c)?; - if t0 > t1 { - std::mem::swap(&mut t0, &mut t1); - } + if t0 > t1 { std::mem::swap(&mut t0, &mut t1); } - if t0 >= 0.0 { - Some(t0) - } else if t1 >= 0.0 { - Some(t1) - } else { - None - } + if t0 >= 0.0 { Some(t0) } + else if t1 >= 0.0 { Some(t1) } + else { None } } fn normal(&self, point: Point3f) -> Unit3f { @@ -89,10 +77,5 @@ impl Surface for Sphere { (*self.texture)(x, y) } - fn bound(&self) -> Option { - Some(Bound { - center: self.center, - radius: self.radius, - }) - } + fn bound(&self) -> Bound { Bound { center: self.center, radius: self.radius, bypass: false } } } diff --git a/src/object/triangle.rs b/src/object/triangle.rs index f04c77e..c66f755 100644 --- a/src/object/triangle.rs +++ b/src/object/triangle.rs @@ -1,10 +1,12 @@ +extern crate nalgebra as na; + use std::cmp::Ordering; -use nalgebra::geometry::Point3; -use nalgebra::*; +use na::*; +use na::geometry::Point3; -use super::{bound::*, Surface}; -use crate::util::*; +use crate::types::*; +use super::{Surface, bound::*}; pub struct Triangle { pub v1: usize, // Handles to 3 vertices. @@ -12,45 +14,35 @@ pub struct Triangle { pub v3: usize, normal: Unit3f, // Precalculated normal vector. - area: f64, // Precalculated area for barycentric calculations. + area: f32, // Precalculated area for barycentric calculations. - texture: Box Texture>, // Texture map. - // Uses barycentric coordinates as input. + texture: Box Texture> // Texture map. + // Uses barycentric coordinates as input. } pub struct TriangleMesh { pub vertices: Vec, - pub triangles: Vec, + pub triangles: Vec } -fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f64 { - let prlg_area: f64 = (b - a).cross(&(c - a)).norm(); +fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f32 { + let prlg_area: f32 = (b - a).cross(&(c - a)).norm(); prlg_area / 2.0 } impl Triangle { - fn vertex1<'a>(&self, vertices: &'a Vec) -> &'a Point3f { - &vertices[self.v1] - } - fn vertex2<'a>(&self, vertices: &'a Vec) -> &'a Point3f { - &vertices[self.v2] - } - fn vertex3<'a>(&self, vertices: &'a Vec) -> &'a Point3f { - &vertices[self.v3] - } + fn vertex1<'a>(&self, vertices: &'a Vec) -> &'a Point3f { &vertices[self.v1] } + fn vertex2<'a>(&self, vertices: &'a Vec) -> &'a Point3f { &vertices[self.v2] } + fn vertex3<'a>(&self, vertices: &'a Vec) -> &'a Point3f { &vertices[self.v3] } // Conversion of barycentric coordinates to // a point on the triangle. - fn from_bary(&self, vertices: &Vec, t: f64, u: f64, v: f64) -> Point3f { - Point::from( - t * self.vertex1(vertices).coords - + u * self.vertex2(vertices).coords - + v * self.vertex3(vertices).coords, - ) + fn from_bary(&self, vertices: &Vec, t: f32, u: f32, v: f32) -> Point3f { + Point::from(t * self.vertex1(vertices).coords + u * self.vertex2(vertices).coords + v * self.vertex3(vertices).coords) } // Conversion of a point to barycentric coordinates. - fn to_bary(&self, vertices: &Vec, point: Point3f) -> (f64, f64, f64) { + fn to_bary(&self, vertices: &Vec, point: Point3f) -> (f32, f32, f32) { let t = tri_area(self.vertex2(vertices), self.vertex3(vertices), &point) / self.area; let u = tri_area(self.vertex1(vertices), self.vertex3(vertices), &point) / self.area; let v = tri_area(self.vertex1(vertices), self.vertex2(vertices), &point) / self.area; @@ -58,39 +50,32 @@ impl Triangle { (t, u, v) } - fn intersect_(&self, vertices: &Vec, ray: Ray) -> Option<(f64, f64, f64)> { + fn intersect_(&self, vertices: &Vec, ray: Ray) -> Option<(f32, f32, f32)> { let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices); let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices); let p_vect = ray.direction.cross(&vect3_1); let det = p_vect.dot(&vect2_1); - if det.abs() < 1e-3 { - return None; - } + if det.abs() < 1e-3 { return None; } let t_vect = ray.origin - self.vertex1(vertices); let u = t_vect.dot(&p_vect) / det; - if u < 0.0 || u > 1.0 { - return None; - } + if u < 0.0 || u > 1.0 { return None; } let q_vect = t_vect.cross(&vect2_1); let v = ray.direction.dot(&q_vect) / det; - if v < 0.0 || (u + v) > 1.0 { - return None; - } + if v < 0.0 || (u + v) > 1.0 { return None; } let t = 1.0 - u - v; Some((t, u, v)) } - fn intersect(&self, vertices: &Vec, ray: Ray) -> Option { - self.intersect_(vertices, ray) - .map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v))) + fn intersect(&self, vertices: &Vec, ray: Ray) -> Option { + self.intersect_(vertices, ray).map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v))) } fn get_texture(&self, vertices: &Vec, point: Point3f) -> Texture { @@ -101,81 +86,45 @@ impl Triangle { #[allow(dead_code)] impl TriangleMesh { - pub fn new( - vertices: Vec, - tris: Vec<(usize, usize, usize, Box Texture>)>, - ) -> Self { - let triangles = tris - .into_iter() - .map(|(v1, v2, v3, f)| Triangle { - v1, - v2, - v3, - normal: Unit::new_normalize( - (&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1])), - ), - area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), - texture: f, - }) - .collect(); + pub fn new(vertices: Vec, tris: Vec<(usize, usize, usize, Box Texture>)>) -> Self { + let triangles = tris.into_iter() + .map(|(v1, v2, v3, f)| Triangle { + v1, v2, v3, + normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))), + area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), + texture: f + }).collect(); TriangleMesh { - vertices, - triangles, + vertices, triangles } } - pub fn new_solid( - vertices: Vec, - tris: Vec<(usize, usize, usize)>, - texture: Texture, - ) -> Self { - let triangles = tris - .into_iter() - .map(|(v1, v2, v3)| Triangle { - v1, - v2, - v3, - normal: Unit::new_normalize( - (&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1])), - ), - area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), - texture: Box::new(move |_, _, _| texture), - }) - .collect(); + pub fn new_solid(vertices: Vec, tris: Vec<(usize, usize, usize)>, texture: Texture) -> Self { + let triangles = tris.into_iter() + .map(|(v1, v2, v3)| Triangle { + v1, v2, v3, + normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))), + area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), + texture: Box::new(move |_, _, _| texture) + }).collect(); TriangleMesh { vertices, - triangles, + triangles } } - pub fn singleton( - vertex1: Point3f, - vertex2: Point3f, - vertex3: Point3f, - texture: F, - ) -> Self - where - F: Fn(f64, f64, f64) -> Texture, - { - TriangleMesh::new( - vec![vertex1, vertex2, vertex3], - vec![(0, 1, 2, Box::new(texture))], - ) - } + pub fn singleton(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: F) -> Self + where F: Fn(f32, f32, f32) -> Texture + { TriangleMesh::new(vec![vertex1, vertex2, vertex3], vec![(0, 1, 2, Box::new(texture))]) } + + pub fn singleton_solid(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: Texture) -> Self + { TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| texture) } - pub fn singleton_solid( - vertex1: Point3f, - vertex2: Point3f, - vertex3: Point3f, - texture: Texture, - ) -> Self { - TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| texture) - } fn closest_tri(&self, point: Point3f) -> &Triangle { - self.triangles - .iter() + self.triangles.iter() .map(move |tri| { + let rel_pos = point - tri.vertex1(&self.vertices); let proj_point3 = rel_pos - (*tri.normal * tri.normal.dot(&rel_pos)); @@ -190,17 +139,15 @@ impl TriangleMesh { (tri, distance(&point, &point_new)) }) .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) - .unwrap() - .0 + .unwrap().0 } } impl Surface for TriangleMesh { - fn intersect(&self, ray: Ray) -> Option { - self.triangles - .iter() - .filter_map(|tri| tri.intersect(&self.vertices, ray)) - .min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)) + fn intersect(&self, ray: Ray) -> Option { + self.triangles.iter() + .filter_map(|tri| tri.intersect(&self.vertices, ray)) + .min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)) } fn normal(&self, point: Point3f) -> Unit3f { @@ -212,115 +159,63 @@ impl Surface for TriangleMesh { } // Uses Welzl's algorithm to solve the bounding sphere problem - fn bound(&self) -> Option { - fn smallest_sphere_plane(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f64) { - if points.len() == 0 || boundary.len() == 3 { - match boundary.len() { - 0 => (Point3::new(0.0, 0.0, 0.0), 0.0), - 1 => (*boundary[0], 0.0), - 2 => { - let half_span = 0.5 * (boundary[1] - boundary[0]); - (*boundary[0] + half_span, half_span.norm()) - } - 3 => triangle_sphere(boundary[0], boundary[1], boundary[2]), - _ => unreachable!(), - } - } else { - let removed = points[0]; - let points = Vec::from(&points[1..]); - - let bound = smallest_sphere(points.clone(), boundary.clone()); - if distance(&bound.0, removed) < bound.1 { - return bound; - } - - let mut boundary = boundary.clone(); - boundary.push(removed); - - smallest_sphere_plane(points, boundary) - } - } - - fn triangle_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f) -> (Point3f, f64) { + fn bound(&self) -> Bound { + fn triangle_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f) -> (Point3f, f32) { let a = point3 - point1; let b = point2 - point1; let crs = b.cross(&a); let to_center = (crs.cross(&b) * a.norm_squared() + a.cross(&crs) * b.norm_squared()) - / (2.0 * crs.norm_squared()); + / (2.0 * crs.norm_squared()); let radius = to_center.norm(); (point1 + to_center, radius) } - fn tetrahedron_sphere( - point1: &Point3f, - point2: &Point3f, - point3: &Point3f, - point4: &Point3f, - ) -> (Point3f, f64) { - let matrix = Matrix4::from_rows(&[ - point1.to_homogeneous().transpose(), - point2.to_homogeneous().transpose(), - point3.to_homogeneous().transpose(), - point4.to_homogeneous().transpose(), - ]); + fn tetrahedron_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f, point4: &Point3f) -> (Point3f, f32) { + let matrix = Matrix4::from_rows(&[point1.to_homogeneous().transpose(), + point2.to_homogeneous().transpose(), + point3.to_homogeneous().transpose(), + point4.to_homogeneous().transpose()]); let a = matrix.determinant() * 2.0; + let mut matrix_mut = matrix.clone(); - if a != 0.0 { - let mut matrix_mut = matrix.clone(); + let squares = Vector4::new(point1.coords.norm_squared(), point2.coords.norm_squared(), point3.coords.norm_squared(), point4.coords.norm_squared()); + matrix_mut.set_column(0, &squares); + let center_x = matrix_mut.determinant(); - let squares = Vector4::new( - point1.coords.norm_squared(), - point2.coords.norm_squared(), - point3.coords.norm_squared(), - point4.coords.norm_squared(), - ); - matrix_mut.set_column(0, &squares); - let center_x = matrix_mut.determinant(); + matrix_mut.set_column(1, &matrix.index((.., 0))); + let center_y = -matrix_mut.determinant(); - matrix_mut.set_column(1, &matrix.index((.., 0))); - let center_y = -matrix_mut.determinant(); + matrix_mut.set_column(2, &matrix.index((.., 1))); + let center_z = matrix_mut.determinant(); - matrix_mut.set_column(2, &matrix.index((.., 1))); - let center_z = matrix_mut.determinant(); + let center = Point3::new(center_x / a, center_y / a, center_z / a); + let radius = distance(point1, ¢er); - let center = Point3::new(center_x / a, center_y / a, center_z / a); - let radius = distance(point1, ¢er); - - (center, radius) - } else { - let points = vec![point1, point2, point3, point4]; - let boundary = Vec::new(); - - smallest_sphere_plane(points, boundary) - } + (center, radius) } - fn smallest_sphere(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f64) { + fn smallest_sphere(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f32) { if points.len() == 0 || boundary.len() == 4 { match boundary.len() { 0 => (Point3::new(0.0, 0.0, 0.0), 0.0), 1 => (*boundary[0], 0.0), - 2 => { - let half_span = 0.5 * (boundary[1] - boundary[0]); - (*boundary[0] + half_span, half_span.norm()) - } + 2 => { let half_span = 0.5 * (boundary[1] - boundary[0]); + (*boundary[0] + half_span, half_span.norm()) }, 3 => triangle_sphere(boundary[0], boundary[1], boundary[2]), 4 => tetrahedron_sphere(boundary[0], boundary[1], boundary[2], boundary[3]), - _ => unreachable!(), + _ => unreachable!() } } else { let removed = points[0]; let points = Vec::from(&points[1..]); let bound = smallest_sphere(points.clone(), boundary.clone()); - if distance(&bound.0, removed) < bound.1 { - return bound; - } + if distance(&bound.0, removed) < bound.1 { return bound; } let mut boundary = boundary.clone(); boundary.push(removed); @@ -330,17 +225,14 @@ impl Surface for TriangleMesh { } extern crate rand; - use rand::seq::SliceRandom; use rand::thread_rng; + use rand::seq::SliceRandom; let mut points: Vec<&Point3f> = self.vertices.iter().collect(); points.shuffle(&mut thread_rng()); let (center, radius) = smallest_sphere(points, Vec::new()); - Some(Bound { - center, - radius: radius + 1e-3, - }) + Bound { center, radius: radius + 1e-3, bypass: false } } } diff --git a/src/render.rs b/src/render.rs index f72f50e..8a6b174 100644 --- a/src/render.rs +++ b/src/render.rs @@ -1,13 +1,18 @@ +extern crate nalgebra as na; + use std::cmp::Ordering; -use std::f64::consts::PI; +use std::f32::consts::PI; + +use na::*; +use na::geometry::Point3; use crate::object::*; -use crate::util::*; +use crate::types::*; -fn trace(ray: Ray, objects: &Vec) -> Option<(&Object, f64)> { - objects - .iter() - .filter_map(|obj| obj.intersect(ray).map(|x| (obj, x))) +fn trace(ray: Ray, objects: &Vec) -> Option<(&Object, f32)> { + objects.iter() + .filter_map(|obj| obj.intersect(ray) + .map(|x| (obj, x))) .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) } @@ -15,10 +20,7 @@ fn light_point(objects: &Vec, obj: &Object, point: Point3f, light: &dyn if light.check_shadow(point, objects) { let texture = obj.get_texture(point); - light.get_color(point) - * (texture.albedo / PI) - * light.intensity(point) - * obj.normal(point).dot(&*light.direction(point)) + light.get_color(point) * (texture.albedo / PI) * light.intensity(point) * obj.normal(point).dot(&*light.direction(point)) } else { // Point is in shadow Color::black() @@ -30,13 +32,8 @@ pub fn cast_ray(ray: Ray, scene: &Scene) -> Color { let point = ray.project(dist); let surface_color = obj.get_texture(point).color; - scene - .lights - .iter() + scene.lights.iter() .map(|light| light_point(&scene.objects, obj, point, &**light)) - .fold(Color::black(), |acc, c| acc + c) - * surface_color - } else { - scene.background - } + .fold(Color::black(), |acc, c| acc + c) * surface_color + } else { scene.background } } diff --git a/src/types.rs b/src/types.rs new file mode 100644 index 0000000..6328873 --- /dev/null +++ b/src/types.rs @@ -0,0 +1,112 @@ +extern crate nalgebra as na; + +use std::ops::{Add, Mul}; + +use na::*; +use na::geometry::Point3; + +pub type Point3f = Point3; +pub type Vector3f = Vector3; +pub type Unit3f = Unit>; + +#[derive(Clone, Copy, Debug)] +pub struct Ray { + pub origin: Point3f, + pub direction: Unit3f +} + +impl Ray { + pub fn from_parts(origin: Point3f, direction: Unit3f) -> Self { + Ray { origin, direction } + } + pub fn new(origin: Point3f, direction: Vector3f) -> Self { Ray::from_parts(origin, Unit::new_normalize(direction)) } + pub fn from_points(origin: Point3f, points_to: Point3f) -> Self { Ray::new(origin, points_to - origin) } + + pub fn project(&self, t: f32) -> Point3f { self.origin + t * self.direction.into_inner() } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Color { + pub red: f32, + pub green: f32, + pub blue: f32, + + _private: () // Private field prevents direct construction +} + +#[allow(dead_code)] +impl Color { + pub fn new(red: f32, green: f32, blue: f32) -> Self { + Color { + red: if red < 0.0 { 0.0 } else { red }, + green: if green < 0.0 { 0.0 } else { green }, + blue: if blue < 0.0 { 0.0 } else { blue }, + + _private: () + } + } + + pub fn to_byte_array(&self) -> [u8; 3] { + let red = (255.0 * self.red) as u8; + let green = (255.0 * self.green) as u8; + let blue = (255.0 * self.blue) as u8; + [red, green, blue] + } + + pub fn gray(brightness: f32) -> Self { Color::new(brightness, brightness, brightness) } + + pub fn black() -> Self { Color::gray(0.0) } + pub fn white() -> Self { Color::gray(1.0) } +} + +impl Add for Color { + type Output = Color; + fn add(self, rhs: Color) -> Color { + Color { + red: self.red + rhs.red, + green: self.green + rhs.green, + blue: self.blue + rhs.blue, + _private: () + } + } +} + +impl Mul for Color { + type Output = Color; + fn mul(self, rhs: Color) -> Color { + Color { + red: self.red * rhs.red, + green: self.green * rhs.green, + blue: self.blue * rhs.blue, + _private: () + } + } +} + +impl Mul for Color { + type Output = Color; + fn mul(self, rhs: f32) -> Color { + Color { + red: self.red * rhs, + green: self.green * rhs, + blue: self.blue * rhs, + _private: () + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct Texture { + pub color: Color, + pub albedo: f32 +} + +#[allow(dead_code)] +impl Texture { + pub fn new(red: f32, green: f32, blue: f32, albedo: f32) -> Self { + Texture { + color: Color::new(red, green, blue), + albedo + } + } +} diff --git a/src/util.rs b/src/util.rs deleted file mode 100644 index 30e7577..0000000 --- a/src/util.rs +++ /dev/null @@ -1,122 +0,0 @@ -use std::ops::{Add, Mul}; - -use nalgebra::geometry::Point3; -use nalgebra::*; - -pub type Point3f = Point3; -pub type Vector3f = Vector3; -pub type Unit3f = Unit>; - -#[derive(Clone, Copy, Debug)] -pub struct Ray { - pub origin: Point3f, - pub direction: Unit3f, -} - -impl Ray { - pub fn from_parts(origin: Point3f, direction: Unit3f) -> Self { - Ray { origin, direction } - } - pub fn new(origin: Point3f, direction: Vector3f) -> Self { - Ray::from_parts(origin, Unit::new_normalize(direction)) - } - pub fn from_points(origin: Point3f, points_to: Point3f) -> Self { - Ray::new(origin, points_to - origin) - } - - pub fn project(&self, t: f64) -> Point3f { - self.origin + t * self.direction.into_inner() - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Color { - pub red: f64, - pub green: f64, - pub blue: f64, - - _private: (), // Private field prevents direct construction -} - -#[allow(dead_code)] -impl Color { - pub fn new(red: f64, green: f64, blue: f64) -> Self { - Color { - red: if red < 0.0 { 0.0 } else { red }, - green: if green < 0.0 { 0.0 } else { green }, - blue: if blue < 0.0 { 0.0 } else { blue }, - - _private: (), - } - } - - pub fn to_byte_array(&self) -> [u8; 3] { - let red = (255.0 * self.red) as u8; - let green = (255.0 * self.green) as u8; - let blue = (255.0 * self.blue) as u8; - [red, green, blue] - } - - pub fn gray(brightness: f64) -> Self { - Color::new(brightness, brightness, brightness) - } - - pub fn black() -> Self { - Color::gray(0.0) - } - pub fn white() -> Self { - Color::gray(1.0) - } -} - -impl Add for Color { - type Output = Color; - fn add(self, rhs: Color) -> Color { - Color { - red: self.red + rhs.red, - green: self.green + rhs.green, - blue: self.blue + rhs.blue, - _private: (), - } - } -} - -impl Mul for Color { - type Output = Color; - fn mul(self, rhs: Color) -> Color { - Color { - red: self.red * rhs.red, - green: self.green * rhs.green, - blue: self.blue * rhs.blue, - _private: (), - } - } -} - -impl Mul for Color { - type Output = Color; - fn mul(self, rhs: f64) -> Color { - Color { - red: self.red * rhs, - green: self.green * rhs, - blue: self.blue * rhs, - _private: (), - } - } -} - -#[derive(Clone, Copy, Debug, PartialEq)] -pub struct Texture { - pub color: Color, - pub albedo: f64, -} - -#[allow(dead_code)] -impl Texture { - pub fn new(red: f64, green: f64, blue: f64, albedo: f64) -> Self { - Texture { - color: Color::new(red, green, blue), - albedo, - } - } -}