Compare commits

..

No commits in common. "f0c8dae74cc2ab977734a47330345d15f1d6c423" and "68f158f3c917971c5430171a3125f5baf1709521" have entirely different histories.

21 changed files with 397 additions and 971 deletions

3
.envrc
View file

@ -1,3 +0,0 @@
#!/usr/bin/env bash
use flake

3
.gitignore vendored
View file

@ -1,6 +1,7 @@
# Generated by Cargo
# will have compiled files and executables
debug/ debug/
target/ target/
.direnv/
# These are backup files generated by rustfmt # These are backup files generated by rustfmt
**/*.rs.bk **/*.rs.bk

24
Cargo.lock generated
View file

@ -40,9 +40,9 @@ checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
[[package]] [[package]]
name = "cfg-if" name = "cfg-if"
version = "1.0.0" version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
[[package]] [[package]]
name = "cloudabi" name = "cloudabi"
@ -61,18 +61,18 @@ checksum = "a06f77d526c1a601b7c4cdd98f54b5eaabffc14d5f2f0296febdc7f357c6d3ba"
[[package]] [[package]]
name = "generic-array" name = "generic-array"
version = "0.12.4" version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ffdf9f34f1447443d37393cc6c2b8313aebddcd96906caf34e54c68d8e57d7bd" checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec"
dependencies = [ dependencies = [
"typenum", "typenum",
] ]
[[package]] [[package]]
name = "getrandom" name = "getrandom"
version = "0.1.16" version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8fc3cb4d91f53b50155bdcfd23f6a4c39ae1969c2ae85982b135750cccaf5fce" checksum = "fc587bc0ec293155d5bfa6b9891ec18a1e330c234f896ea47fbada4cadbe47e6"
dependencies = [ dependencies = [
"cfg-if", "cfg-if",
"libc", "libc",
@ -81,9 +81,9 @@ dependencies = [
[[package]] [[package]]
name = "libc" name = "libc"
version = "0.2.94" version = "0.2.80"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "18794a8ad5b29321f790b55d93dfba91e125cb1a9edbd4f8e3150acc771c1a5e" checksum = "4d58d1b70b004888f764dfbf6a26a3b0342a1632d33968e4a179d8011c760614"
[[package]] [[package]]
name = "libm" name = "libm"
@ -93,9 +93,9 @@ checksum = "c7d73b3f436185384286bd8098d17ec07c9a7d2388a6599f824d8502b529702a"
[[package]] [[package]]
name = "matrixmultiply" name = "matrixmultiply"
version = "0.2.4" version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "916806ba0031cd542105d916a97c8572e1fa6dd79c9c51e7eb43a09ec2dd84c1" checksum = "d4f7ec66360130972f34830bfad9ef05c6610a43938a467bcc9ab9369ab3478f"
dependencies = [ dependencies = [
"rawpointer", "rawpointer",
] ]
@ -336,9 +336,9 @@ dependencies = [
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.13.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f6906492a7cd215bfa4cf595b600146ccfac0c79bcbd1f3000162af5e8b06" checksum = "373c8a200f9e67a0c95e62a4f52fbf80c23b4381c05a17845531982fa99e6b33"
[[package]] [[package]]
name = "wasi" name = "wasi"

View file

@ -1,9 +1,11 @@
[package] [package]
name = "render" name = "render"
version = "0.1.0" version = "0.1.0"
authors = ["Kiana Sheibani"] authors = ["Bijan Sheibani"]
edition = "2018" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
nalgebra = "0.18" nalgebra = "0.18"
rand = "0.7.3" rand = "0.7.3"

21
LICENSE
View file

@ -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.

View file

@ -1,5 +1,4 @@
# rust-render # rust-render
A raytracing 3D renderer written in Rust. A raytracing 3D renderer written in Rust.
### Checklist of features ### Checklist of features

285
flake.lock generated
View file

@ -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
}

View file

@ -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 ];
};
}

View file

@ -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 ];
});
};
}

View file

@ -1,2 +0,0 @@
[toolchain]
channel = "nightly-2024-10-01"

View file

@ -1,121 +1,87 @@
use nalgebra::geometry::{Point2, Point3}; extern crate nalgebra as na;
use nalgebra::*;
use crate::util::Ray; use na::*;
use na::geometry::{Point2, Point3};
use crate::types::Ray;
#[derive(Debug)] #[derive(Debug)]
pub struct Camera { pub struct Camera {
matrix: Isometry3<f64>, // The transformation that stores the matrix: Isometry3<f32>, // The transformation that stores the
// position and orientation of the camera. (Not actually a matrix, but w/e) // 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<f64>, // The size of the canvas within the world space.
pub image_size: Vector2<u64>, // The size of the final image in pixels. focal_length: f32, // The distance from the camera origin to the canvas.
canvas_size: Vector2<f32>, // The size of the canvas within the world space.
pub image_size: Vector2<u32> // The size of the final image in pixels.
} }
impl Camera { impl Camera {
// Constructs a new camera from a position and viewing direction. // Constructs a new camera from a position and viewing direction.
pub fn new_( pub fn new_(pos: Point3<f32>, dir: Vector3<f32>, up: Vector3<f32>,
pos: Point3<f64>, focal_length: f32, aspect_ratio: f32, canvas_y: f32, image_y: u32) -> Self {
dir: Vector3<f64>,
up: Vector3<f64>,
focal_length: f64,
aspect_ratio: f64,
canvas_y: f64,
image_y: u64,
) -> Self {
let iso = Isometry3::face_towards(&pos, &(pos + dir), &up); let iso = Isometry3::face_towards(&pos, &(pos + dir), &up);
Camera { Camera {
matrix: iso, matrix: iso,
focal_length: focal_length, focal_length: focal_length,
canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y), canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y),
image_size: Vector2::new((image_y as f64 * aspect_ratio) as u64, image_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 // Constructs a new camera from a position and viewing direction
// (assuming the camera is oriented upright). // (assuming the camera is oriented upright).
pub fn new( pub fn new(pos: Point3<f32>, dir: Vector3<f32>,
pos: Point3<f64>, focal_length: f32, aspect_ratio: f32, canvas_y: f32, image_y: u32) -> Self
dir: Vector3<f64>, { Camera::new_(pos, dir, Vector3::y(), focal_length, aspect_ratio, canvas_y, image_y) }
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 pos(&self) -> Point3<f64> { pub fn pos(&self) -> Point3<f32> { Point3::from(self.matrix.translation.vector) }
Point3::from(self.matrix.translation.vector)
}
// Takes a 2D point in the image space and // Takes a 2D point in the image space and
// maps it to the 3D point on the canvas. // maps it to the 3D point on the canvas.
fn project(&self, x: u64, y: u64) -> Point3<f64> { fn project(&self, x: u32, y: u32) -> Point3<f32> {
// convert point from raster coordinates to center-based coordinates // convert point from raster coordinates to center-based coordinates
let pixelndc = Point2::new( 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);
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 point: Point3<f64> = Point::from( let point: Point3<f32> = Point::from(pixelndc.coords.component_div(&self.image_size.map(|x| x as f32))
pixelndc
.coords
.component_div(&self.image_size.map(|x| x as f64))
.component_mul(&self.canvas_size) .component_mul(&self.canvas_size)
.fixed_resize(self.focal_length), .fixed_resize(self.focal_length));
);
self.matrix * point self.matrix * point
} }
// Takes a 2D point in the image space and // Takes a 2D point in the image space and
// returns a ray in the world space, for use in raytracing. // 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)) Ray::from_points(self.pos(), self.project(x, y))
} }
} }
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
fn round(point: Point3<f64>) -> Point3<f64> { fn round(point: Point3<f32>) -> Point3<f32> {
Point::from(point.coords.map(|x| x.round())) Point::from(point.coords.map(|x| x.round()))
} }
#[test] #[test]
fn camera_pos() { fn camera_pos() {
let camera: Camera = Camera::new( let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0),
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0),
1.0, 1.0, 1.0,
1.0, 2.0, 800);
2.0,
800,
);
assert_eq!(camera.pos(), Point3::new(-5.0, 0.0, 0.0)); assert_eq!(camera.pos(), Point3::new(-5.0, 0.0, 0.0));
} }
#[test] #[test]
fn camera_matrix1() { fn camera_matrix1() {
let camera: Camera = Camera::new( let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0),
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0),
1.0, 1.0, 1.0,
1.0, 2.0, 800);
2.0,
800,
);
let point = Point3::new(0.0, 0.0, 4.0); let point = Point3::new(0.0, 0.0, 4.0);
let point = camera.matrix * point; let point = camera.matrix * point;
@ -125,14 +91,10 @@ mod tests {
#[test] #[test]
fn camera_matrix2() { fn camera_matrix2() {
let camera: Camera = Camera::new( let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0),
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0),
1.0, 1.0, 1.0,
1.0, 2.0, 800);
2.0,
800,
);
let point = Point3::new(4.0, 0.0, 0.0); let point = Point3::new(4.0, 0.0, 0.0);
let point = camera.matrix * point; let point = camera.matrix * point;
@ -142,14 +104,10 @@ mod tests {
#[test] #[test]
fn camera_project1() { fn camera_project1() {
let camera: Camera = Camera::new( let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0),
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0),
1.0, 1.0, 1.0,
1.0, 2.0, 800);
2.0,
800,
);
let point = camera.project(400, 400); let point = camera.project(400, 400);
let point = round(point); // round to avoid errors let point = round(point); // round to avoid errors
@ -158,14 +116,10 @@ mod tests {
#[test] #[test]
fn camera_project2() { fn camera_project2() {
let camera: Camera = Camera::new( let camera: Camera = Camera::new(Point3::new(-5.0, 0.0, 0.0),
Point3::new(-5.0, 0.0, 0.0),
Vector3::new(1.0, 0.0, 0.0), Vector3::new(1.0, 0.0, 0.0),
1.0, 1.0, 1.0,
1.0, 2.0, 800);
2.0,
800,
);
let point = camera.project(0, 0); let point = camera.project(0, 0);
let point = round(point); // round to avoid errors let point = round(point); // round to avoid errors

View file

@ -1,17 +1,15 @@
extern crate nalgebra as na;
use std::time::Instant;
use std::fs::File; use std::fs::File;
use std::io::Write; use std::io::Write;
use std::time::Instant;
use nalgebra::*; use na::*;
mod camera; mod camera; use camera::*;
use camera::*; mod types; use types::*;
mod util; mod object; use object::*;
use util::*; mod render; use render::*;
mod object;
use object::*;
mod render;
use render::*;
fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()> { fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()> {
let width = camera.image_size.x; let width = camera.image_size.x;
@ -37,22 +35,18 @@ fn render(camera: &Camera, scene: &Scene, filename: &str) -> std::io::Result<()>
} }
fn main() -> std::io::Result<()> { fn main() -> std::io::Result<()> {
let camera = Camera::new(
Point3::new(0.0, 5.0, 0.0), 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);
Vector3::new(0.0, -1.0, 0.0),
1.0,
16.0 / 9.0,
2.0,
720,
);
let scene = Scene { let scene = Scene {
objects: vec![Object::new(Plane::xz(|_, _| Texture { objects: vec![
color: Color::white(), Object::new(Plane::xz(|_, _| Texture { color: Color::white(), albedo: 0.8 })),
albedo: 0.8,
}))], ],
lights: vec![], lights: vec![
background: Color::gray(0.5),
],
background: Color::gray(0.5)
}; };
let before = Instant::now(); let before = Instant::now();

View file

@ -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. // A trait for types that can be in Objects.
pub trait Surface { pub trait Surface {
// Takes in a ray and performs an intersection test // Takes in a ray and performs an intersection test
// on itself. If the ray intersects the object, // on itself. If the ray intersects the object,
// returns the distance to the intersection point. // returns the distance to the intersection point.
fn intersect(&self, ray: Ray) -> Option<f64>; fn intersect(&self, ray: Ray) -> Option<f32>;
// Takes in a point (assumed to be on the object's surface) // Takes in a point (assumed to be on the object's surface)
// and returns the normal vector off of that point. // and returns the normal vector off of that point.
@ -27,12 +24,12 @@ pub trait Surface {
fn get_texture(&self, point: Point3f) -> Texture; fn get_texture(&self, point: Point3f) -> Texture;
// Creates a bounding sphere around the object. // Creates a bounding sphere around the object.
fn bound(&self) -> Option<Bound>; fn bound(&self) -> Bound;
} }
pub struct Object { pub struct Object {
pub surface: Box<dyn Surface>, pub surface: Box<dyn Surface>,
bound: Option<Bound>, bound: Bound
} }
impl Object { impl Object {
@ -41,23 +38,18 @@ impl Object {
let bound = surface.bound(); let bound = surface.bound();
Object { Object {
surface: Box::new(surface), surface: Box::new(surface),
bound, bound
} }
} }
pub fn intersect(&self, ray: Ray) -> Option<f64> {
if !self.bound.as_ref().is_some_and(|b| !b.is_intersected(ray)) { pub fn intersect(&self, ray: Ray) -> Option<f32> {
if self.bound.is_intersected(ray) {
self.surface.intersect(ray) self.surface.intersect(ray)
} else { } else { None }
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 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 { pub trait Light {
@ -68,7 +60,7 @@ pub trait Light {
fn get_color(&self, point: Point3f) -> Color; fn get_color(&self, point: Point3f) -> Color;
// Compute intensity on a point. // 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. // Return the direction from the point to the light source.
fn direction(&self, point: Point3f) -> Unit3f; fn direction(&self, point: Point3f) -> Unit3f;
@ -77,5 +69,5 @@ pub trait Light {
pub struct Scene { pub struct Scene {
pub objects: Vec<Object>, pub objects: Vec<Object>,
pub lights: Vec<Box<dyn Light>>, pub lights: Vec<Box<dyn Light>>,
pub background: Color, pub background: Color
} }

View file

@ -1,21 +1,30 @@
// use na::distance; extern crate nalgebra as na;
use nalgebra::geometry::Point3;
use crate::util::*; // use na::distance;
use na::geometry::Point3;
use crate::types::*;
// A bounding sphere, used for // A bounding sphere, used for
// intersection test optimization. // intersection test optimization.
#[derive(Debug)] #[derive(Debug)]
pub struct Bound { pub struct Bound {
pub center: Point3f, pub center: Point3f,
pub radius: f64, pub radius: f32,
// If true, then the bounding sphere is disabled.
pub bypass: bool
} }
impl Bound { impl Bound {
pub fn is_intersected(&self, ray: Ray) -> bool { pub fn is_intersected(&self, ray: Ray) -> bool {
if self.bypass { return true; }
let l = ray.origin - self.center; let l = ray.origin - self.center;
l.norm_squared() >= self.radius * self.radius l.norm_squared() >= self.radius * self.radius
} }
// pub fn contains(&self, point: &Point3f) -> bool { distance(&self.center, point) < 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 } }
} }

View file

@ -1,8 +1,10 @@
use nalgebra::geometry::Point3; extern crate nalgebra as na;
use nalgebra::*;
use super::{bound::*, Surface}; use na::*;
use crate::util::*; use na::geometry::Point3;
use crate::types::*;
use super::{Surface, bound::*};
pub struct Plane { pub struct Plane {
pub center: Point3f, // Plane origin (used for texture mapping). pub center: Point3f, // Plane origin (used for texture mapping).
@ -11,7 +13,7 @@ pub struct Plane {
x_axis: Vector3f, // Plane x-axis (The 3D direction that corresponds to the x-direction on the plane). 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). y_axis: Vector3f, // Plane y-axis (The 3D direction that corresponds to the y-direction on the plane).
texture: Box<dyn Fn(f64, f64) -> Texture>, // Texture map. texture: Box<dyn Fn(f32, f32) -> Texture> // Texture map.
// Input coordinates are defined in terms of the axes above. // Input coordinates are defined in terms of the axes above.
} }
@ -19,87 +21,61 @@ pub struct Plane {
impl Plane { impl Plane {
// Creates a new plane. // Creates a new plane.
pub fn new<F: 'static>(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self pub fn new<F: 'static>(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self
where where F: Fn(f32, f32) -> Texture
F: Fn(f64, f64) -> Texture,
{ {
Plane { Plane {
center, center,
normal: Unit::new_normalize(x_axis.cross(&y_axis)), normal: Unit::new_normalize(x_axis.cross(&y_axis)),
x_axis: x_axis, x_axis: x_axis,
y_axis: y_axis, y_axis: y_axis,
texture: Box::new(texture), texture: Box::new(texture)
} }
} }
// Creates a new plane with the normal flipped. // Creates a new plane with the normal flipped.
pub fn new_flip<F: 'static>( pub fn new_flip<F: 'static>(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self
center: Point3f, where F: Fn(f32, f32) -> Texture
x_axis: Vector3f,
y_axis: Vector3f,
texture: F,
) -> Self
where
F: Fn(f64, f64) -> Texture,
{ {
Plane { Plane {
center: center, center: center,
normal: Unit::new_normalize(y_axis.cross(&x_axis)), normal: Unit::new_normalize(y_axis.cross(&x_axis)),
x_axis: x_axis, x_axis: x_axis,
y_axis: y_axis, y_axis: y_axis,
texture: Box::new(texture), texture: Box::new(texture)
} }
} }
// Creates a new plane of a solid color. // Creates a new plane of a solid color.
pub fn new_solid( pub fn new_solid(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self
center: Point3f, { Plane::new(center, x_axis, y_axis, move |_, _| texture) }
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. // Creates a new flipped plane of a solid color.
pub fn new_solid_flip( pub fn new_solid_flip(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: Texture) -> Self
center: Point3f, { Plane::new_flip(center, x_axis, y_axis, move |_, _| texture) }
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. // Creates a new XY-plane with the given texture map.
pub fn xy(texture: impl 'static + Fn(f64, f64) -> Texture) -> Self { pub fn xy(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self
Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) { Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) }
}
// Creates a new XZ-plane with the given texture map. // Creates a new XZ-plane with the given texture map.
pub fn xz(texture: impl 'static + Fn(f64, f64) -> Texture) -> Self { pub fn xz(texture: impl 'static + Fn(f32, f32) -> Texture) -> Self
Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) { Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) }
}
} }
impl Surface for Plane { impl Surface for Plane {
fn intersect(&self, ray: Ray) -> Option<f64> { fn intersect(&self, ray: Ray) -> Option<f32> {
let d = self.normal.dot(&ray.direction); let d = self.normal.dot(&ray.direction);
if d > -1e-3 { if d < 1e-3 { return None; }
return None;
}
let t = (self.center - ray.origin).dot(&*self.normal) / d; let t = (self.center - ray.origin).dot(&*self.normal) / d;
if t >= 0.0 { if t >= 0.0 { Some(t) }
Some(t) else { None }
} else {
None
}
} }
fn normal(&self, _point: Point3f) -> Unit3f { fn normal(&self, _point: Point3f) -> Unit3f { self.normal }
self.normal
}
fn get_texture(&self, point: Point3f) -> Texture { fn get_texture(&self, point: Point3f) -> Texture {
let rel_pos = point - self.center; let rel_pos = point - self.center;
@ -111,8 +87,7 @@ impl Surface for Plane {
(*self.texture)(x, y) (*self.texture)(x, y)
} }
// Planes are infinite, so no finite bounding sphere could possibly contain one // Planes are infinite, so no finite
fn bound(&self) -> Option<Bound> { // bounding sphere could possibly contain one.
None fn bound(&self) -> Bound { Bound::bypass() }
}
} }

View file

@ -1,41 +1,34 @@
use nalgebra::*; extern crate nalgebra as na;
use na::*;
use crate::types::*;
use super::*; use super::*;
use crate::util::*;
pub struct PointLight { pub struct PointLight {
pub pos: Point3f, pub pos: Point3f,
pub color: Color, pub color: Color,
pub intensity: f64, pub intensity: f32
} }
#[allow(dead_code)] #[allow(dead_code)]
impl PointLight { impl PointLight {
pub fn new(pos: Point3f, color: Color, intensity: f64) -> PointLight { pub fn new(pos: Point3f, color: Color, intensity: f32) -> PointLight {
PointLight { PointLight { pos, color, intensity }
pos,
color,
intensity,
}
} }
} }
impl Light for PointLight { impl Light for PointLight {
fn check_shadow(&self, point: Point3f, objects: &Vec<Object>) -> bool { fn check_shadow(&self, point: Point3f, objects: &Vec<Object>) -> bool {
let max_d = distance(&self.pos, &point); let max_d = distance(&self.pos, &point);
objects objects.iter()
.iter()
.filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point))) .filter_map(|obj| obj.intersect(Ray::from_points(self.pos, point)))
.all(|d| d - max_d > -1e-3 ) .all(|d| d - max_d > -1e-3 )
} }
fn get_color(&self, _point: Point3f) -> Color { fn get_color(&self, _point: Point3f) -> Color { self.color }
self.color
}
fn intensity(&self, _point: Point3f) -> f64 { fn intensity(&self, _point: Point3f) -> f32 { self.intensity }
self.intensity
}
fn direction(&self, point: Point3f) -> Unit3f { fn direction(&self, point: Point3f) -> Unit3f {
Unit::new_normalize(self.pos - point) Unit::new_normalize(self.pos - point)
@ -49,13 +42,7 @@ mod tests {
#[test] #[test]
fn point_light_check_shadow() { fn point_light_check_shadow() {
let light = PointLight::new(Point3::new(0.0, 1.0, 0.0), Color::white(), 1.0); let light = PointLight::new(Point3::new(0.0, 1.0, 0.0), Color::white(), 1.0);
let block = Object::new(Sphere::new_solid( 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)));
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::new()));
assert!(!light.check_shadow(Point3::origin(), &vec![block])); assert!(!light.check_shadow(Point3::origin(), &vec![block]));

View file

@ -1,55 +1,49 @@
use std::f64::consts::PI; extern crate nalgebra as na;
use nalgebra::geometry::Point3; use std::f32::consts::PI;
use nalgebra::*;
use super::{bound::*, Surface}; use na::*;
use crate::util::*; use na::geometry::Point3;
use crate::types::*;
use super::{Surface, bound::*};
pub struct Sphere { pub struct Sphere {
pub center: Point3f, // Center point of the 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<dyn Fn(f64, f64) -> Texture>, // Texture map. texture: Box<dyn Fn(f32, f32) -> Texture> // Texture map.
// Uses spherical coordinates (normalized from 0-1) as input. // Uses spherical coordinates (normalized from 0-1) as input.
} }
#[allow(dead_code)] #[allow(dead_code)]
impl Sphere { impl Sphere {
// Creates a new sphere. // Creates a new sphere.
pub fn new<F: 'static>(x: f64, y: f64, z: f64, radius: f64, texture: F) -> Self pub fn new<F: 'static>(x: f32, y: f32, z: f32, radius: f32, texture: F) -> Self
where where F: Fn(f32, f32) -> Texture
F: Fn(f64, f64) -> Texture,
{ {
Sphere { Sphere {
center: Point3::new(x, y, z), center: Point3::new(x, y, z), radius,
radius, texture: Box::new(texture)
texture: Box::new(texture),
} }
} }
// Creates a new sphere of a solid color. // Creates a new sphere of a solid color.
pub fn new_solid(x: f64, y: f64, z: f64, radius: f64, texture: Texture) -> Self { pub fn new_solid(x: f32, y: f32, z: f32, radius: f32, texture: Texture) -> Self
Sphere::new(x, y, z, radius, move |_, _| texture) { Sphere::new(x, y, z, radius, move |_, _| texture) }
}
} }
impl Surface for Sphere { impl Surface for Sphere {
fn intersect(&self, ray: Ray) -> Option<f64> { fn intersect(&self, ray: Ray) -> Option<f32> {
fn solve_quadratic(b: f64, c: f64) -> Option<(f64, f64)> { fn solve_quadratic(b: f32, c: f32) -> Option<(f32, f32)> {
let discr = b * b - 4.0 * c; let discr = b * b - 4.0 * c;
if discr < 0.0 { if discr < 0.0 { None }
None else if discr == 0.0 {
} else if discr == 0.0 {
let x = -0.5 * b; let x = -0.5 * b;
Some((x, x)) Some((x, x))
} else { } else {
let q = if b > 0.0 { let q = if b > 0.0 { -0.5 * (b + discr.sqrt()) } else { -0.5 * (b - discr.sqrt()) };
-0.5 * (b + discr.sqrt())
} else {
-0.5 * (b - discr.sqrt())
};
Some((q, c / q)) Some((q, c / q))
} }
} }
@ -60,17 +54,11 @@ impl Surface for Sphere {
let (mut t0, mut t1) = solve_quadratic(b, c)?; let (mut t0, mut t1) = solve_quadratic(b, c)?;
if t0 > t1 { if t0 > t1 { std::mem::swap(&mut t0, &mut t1); }
std::mem::swap(&mut t0, &mut t1);
}
if t0 >= 0.0 { if t0 >= 0.0 { Some(t0) }
Some(t0) else if t1 >= 0.0 { Some(t1) }
} else if t1 >= 0.0 { else { None }
Some(t1)
} else {
None
}
} }
fn normal(&self, point: Point3f) -> Unit3f { fn normal(&self, point: Point3f) -> Unit3f {
@ -89,10 +77,5 @@ impl Surface for Sphere {
(*self.texture)(x, y) (*self.texture)(x, y)
} }
fn bound(&self) -> Option<Bound> { fn bound(&self) -> Bound { Bound { center: self.center, radius: self.radius, bypass: false } }
Some(Bound {
center: self.center,
radius: self.radius,
})
}
} }

View file

@ -1,10 +1,12 @@
extern crate nalgebra as na;
use std::cmp::Ordering; use std::cmp::Ordering;
use nalgebra::geometry::Point3; use na::*;
use nalgebra::*; use na::geometry::Point3;
use super::{bound::*, Surface}; use crate::types::*;
use crate::util::*; use super::{Surface, bound::*};
pub struct Triangle { pub struct Triangle {
pub v1: usize, // Handles to 3 vertices. pub v1: usize, // Handles to 3 vertices.
@ -12,45 +14,35 @@ pub struct Triangle {
pub v3: usize, pub v3: usize,
normal: Unit3f, // Precalculated normal vector. normal: Unit3f, // Precalculated normal vector.
area: f64, // Precalculated area for barycentric calculations. area: f32, // Precalculated area for barycentric calculations.
texture: Box<dyn Fn(f64, f64, f64) -> Texture>, // Texture map. texture: Box<dyn Fn(f32, f32, f32) -> Texture> // Texture map.
// Uses barycentric coordinates as input. // Uses barycentric coordinates as input.
} }
pub struct TriangleMesh { pub struct TriangleMesh {
pub vertices: Vec<Point3f>, pub vertices: Vec<Point3f>,
pub triangles: Vec<Triangle>, pub triangles: Vec<Triangle>
} }
fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f64 { fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f32 {
let prlg_area: f64 = (b - a).cross(&(c - a)).norm(); let prlg_area: f32 = (b - a).cross(&(c - a)).norm();
prlg_area / 2.0 prlg_area / 2.0
} }
impl Triangle { impl Triangle {
fn vertex1<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { fn vertex1<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { &vertices[self.v1] }
&vertices[self.v1] fn vertex2<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { &vertices[self.v2] }
} fn vertex3<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { &vertices[self.v3] }
fn vertex2<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f {
&vertices[self.v2]
}
fn vertex3<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f {
&vertices[self.v3]
}
// Conversion of barycentric coordinates to // Conversion of barycentric coordinates to
// a point on the triangle. // a point on the triangle.
fn from_bary(&self, vertices: &Vec<Point3f>, t: f64, u: f64, v: f64) -> Point3f { fn from_bary(&self, vertices: &Vec<Point3f>, t: f32, u: f32, v: f32) -> Point3f {
Point::from( Point::from(t * self.vertex1(vertices).coords + u * self.vertex2(vertices).coords + v * self.vertex3(vertices).coords)
t * self.vertex1(vertices).coords
+ u * self.vertex2(vertices).coords
+ v * self.vertex3(vertices).coords,
)
} }
// Conversion of a point to barycentric coordinates. // Conversion of a point to barycentric coordinates.
fn to_bary(&self, vertices: &Vec<Point3f>, point: Point3f) -> (f64, f64, f64) { fn to_bary(&self, vertices: &Vec<Point3f>, point: Point3f) -> (f32, f32, f32) {
let t = tri_area(self.vertex2(vertices), self.vertex3(vertices), &point) / self.area; 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 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; let v = tri_area(self.vertex1(vertices), self.vertex2(vertices), &point) / self.area;
@ -58,39 +50,32 @@ impl Triangle {
(t, u, v) (t, u, v)
} }
fn intersect_(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<(f64, f64, f64)> { fn intersect_(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<(f32, f32, f32)> {
let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices); let vect2_1 = self.vertex2(vertices) - self.vertex1(vertices);
let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices); let vect3_1 = self.vertex3(vertices) - self.vertex1(vertices);
let p_vect = ray.direction.cross(&vect3_1); let p_vect = ray.direction.cross(&vect3_1);
let det = p_vect.dot(&vect2_1); let det = p_vect.dot(&vect2_1);
if det.abs() < 1e-3 { if det.abs() < 1e-3 { return None; }
return None;
}
let t_vect = ray.origin - self.vertex1(vertices); let t_vect = ray.origin - self.vertex1(vertices);
let u = t_vect.dot(&p_vect) / det; let u = t_vect.dot(&p_vect) / det;
if u < 0.0 || u > 1.0 { if u < 0.0 || u > 1.0 { return None; }
return None;
}
let q_vect = t_vect.cross(&vect2_1); let q_vect = t_vect.cross(&vect2_1);
let v = ray.direction.dot(&q_vect) / det; let v = ray.direction.dot(&q_vect) / det;
if v < 0.0 || (u + v) > 1.0 { if v < 0.0 || (u + v) > 1.0 { return None; }
return None;
}
let t = 1.0 - u - v; let t = 1.0 - u - v;
Some((t, u, v)) Some((t, u, v))
} }
fn intersect(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<f64> { fn intersect(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<f32> {
self.intersect_(vertices, ray) self.intersect_(vertices, ray).map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v)))
.map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v)))
} }
fn get_texture(&self, vertices: &Vec<Point3f>, point: Point3f) -> Texture { fn get_texture(&self, vertices: &Vec<Point3f>, point: Point3f) -> Texture {
@ -101,81 +86,45 @@ impl Triangle {
#[allow(dead_code)] #[allow(dead_code)]
impl TriangleMesh { impl TriangleMesh {
pub fn new( pub fn new(vertices: Vec<Point3f>, tris: Vec<(usize, usize, usize, Box<dyn Fn(f32, f32, f32) -> Texture>)>) -> Self {
vertices: Vec<Point3f>, let triangles = tris.into_iter()
tris: Vec<(usize, usize, usize, Box<dyn Fn(f64, f64, f64) -> Texture>)>,
) -> Self {
let triangles = tris
.into_iter()
.map(|(v1, v2, v3, f)| Triangle { .map(|(v1, v2, v3, f)| Triangle {
v1, v1, v2, v3,
v2, normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))),
v3,
normal: Unit::new_normalize(
(&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1])),
),
area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]),
texture: f, texture: f
}) }).collect();
.collect();
TriangleMesh { TriangleMesh {
vertices, vertices, triangles
triangles,
} }
} }
pub fn new_solid( pub fn new_solid(vertices: Vec<Point3f>, tris: Vec<(usize, usize, usize)>, texture: Texture) -> Self {
vertices: Vec<Point3f>, let triangles = tris.into_iter()
tris: Vec<(usize, usize, usize)>,
texture: Texture,
) -> Self {
let triangles = tris
.into_iter()
.map(|(v1, v2, v3)| Triangle { .map(|(v1, v2, v3)| Triangle {
v1, v1, v2, v3,
v2, normal: Unit::new_normalize((&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1]))),
v3,
normal: Unit::new_normalize(
(&vertices[v2] - &vertices[v1]).cross(&(&vertices[v3] - &vertices[v1])),
),
area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]), area: tri_area(&vertices[v1], &vertices[v2], &vertices[v3]),
texture: Box::new(move |_, _, _| texture), texture: Box::new(move |_, _, _| texture)
}) }).collect();
.collect();
TriangleMesh { TriangleMesh {
vertices, vertices,
triangles, triangles
} }
} }
pub fn singleton<F: 'static>( pub fn singleton<F: 'static>(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: F) -> Self
vertex1: Point3f, where F: Fn(f32, f32, f32) -> Texture
vertex2: Point3f, { TriangleMesh::new(vec![vertex1, vertex2, vertex3], vec![(0, 1, 2, Box::new(texture))]) }
vertex3: Point3f,
texture: F, pub fn singleton_solid(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: Texture) -> Self
) -> Self { TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| texture) }
where
F: Fn(f64, f64, f64) -> 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)
}
fn closest_tri(&self, point: Point3f) -> &Triangle { fn closest_tri(&self, point: Point3f) -> &Triangle {
self.triangles self.triangles.iter()
.iter()
.map(move |tri| { .map(move |tri| {
let rel_pos = point - tri.vertex1(&self.vertices); let rel_pos = point - tri.vertex1(&self.vertices);
let proj_point3 = rel_pos - (*tri.normal * tri.normal.dot(&rel_pos)); let proj_point3 = rel_pos - (*tri.normal * tri.normal.dot(&rel_pos));
@ -190,15 +139,13 @@ impl TriangleMesh {
(tri, distance(&point, &point_new)) (tri, distance(&point, &point_new))
}) })
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal))
.unwrap() .unwrap().0
.0
} }
} }
impl Surface for TriangleMesh { impl Surface for TriangleMesh {
fn intersect(&self, ray: Ray) -> Option<f64> { fn intersect(&self, ray: Ray) -> Option<f32> {
self.triangles self.triangles.iter()
.iter()
.filter_map(|tri| tri.intersect(&self.vertices, ray)) .filter_map(|tri| tri.intersect(&self.vertices, ray))
.min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal)) .min_by(|a, b| a.partial_cmp(&b).unwrap_or(Ordering::Equal))
} }
@ -212,36 +159,8 @@ impl Surface for TriangleMesh {
} }
// Uses Welzl's algorithm to solve the bounding sphere problem // Uses Welzl's algorithm to solve the bounding sphere problem
fn bound(&self) -> Option<Bound> { fn bound(&self) -> Bound {
fn smallest_sphere_plane(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f64) { fn triangle_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f) -> (Point3f, f32) {
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) {
let a = point3 - point1; let a = point3 - point1;
let b = point2 - point1; let b = point2 - point1;
@ -255,30 +174,16 @@ impl Surface for TriangleMesh {
(point1 + to_center, radius) (point1 + to_center, radius)
} }
fn tetrahedron_sphere( fn tetrahedron_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f, point4: &Point3f) -> (Point3f, f32) {
point1: &Point3f, let matrix = Matrix4::from_rows(&[point1.to_homogeneous().transpose(),
point2: &Point3f,
point3: &Point3f,
point4: &Point3f,
) -> (Point3f, f64) {
let matrix = Matrix4::from_rows(&[
point1.to_homogeneous().transpose(),
point2.to_homogeneous().transpose(), point2.to_homogeneous().transpose(),
point3.to_homogeneous().transpose(), point3.to_homogeneous().transpose(),
point4.to_homogeneous().transpose(), point4.to_homogeneous().transpose()]);
]);
let a = matrix.determinant() * 2.0; let a = matrix.determinant() * 2.0;
if a != 0.0 {
let mut matrix_mut = matrix.clone(); let mut matrix_mut = matrix.clone();
let squares = Vector4::new( let squares = Vector4::new(point1.coords.norm_squared(), point2.coords.norm_squared(), point3.coords.norm_squared(), point4.coords.norm_squared());
point1.coords.norm_squared(),
point2.coords.norm_squared(),
point3.coords.norm_squared(),
point4.coords.norm_squared(),
);
matrix_mut.set_column(0, &squares); matrix_mut.set_column(0, &squares);
let center_x = matrix_mut.determinant(); let center_x = matrix_mut.determinant();
@ -292,35 +197,25 @@ impl Surface for TriangleMesh {
let radius = distance(point1, &center); let radius = distance(point1, &center);
(center, radius) (center, radius)
} else {
let points = vec![point1, point2, point3, point4];
let boundary = Vec::new();
smallest_sphere_plane(points, boundary)
}
} }
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 { if points.len() == 0 || boundary.len() == 4 {
match boundary.len() { match boundary.len() {
0 => (Point3::new(0.0, 0.0, 0.0), 0.0), 0 => (Point3::new(0.0, 0.0, 0.0), 0.0),
1 => (*boundary[0], 0.0), 1 => (*boundary[0], 0.0),
2 => { 2 => { let half_span = 0.5 * (boundary[1] - boundary[0]);
let half_span = 0.5 * (boundary[1] - boundary[0]); (*boundary[0] + half_span, half_span.norm()) },
(*boundary[0] + half_span, half_span.norm())
}
3 => triangle_sphere(boundary[0], boundary[1], boundary[2]), 3 => triangle_sphere(boundary[0], boundary[1], boundary[2]),
4 => tetrahedron_sphere(boundary[0], boundary[1], boundary[2], boundary[3]), 4 => tetrahedron_sphere(boundary[0], boundary[1], boundary[2], boundary[3]),
_ => unreachable!(), _ => unreachable!()
} }
} else { } else {
let removed = points[0]; let removed = points[0];
let points = Vec::from(&points[1..]); let points = Vec::from(&points[1..]);
let bound = smallest_sphere(points.clone(), boundary.clone()); let bound = smallest_sphere(points.clone(), boundary.clone());
if distance(&bound.0, removed) < bound.1 { if distance(&bound.0, removed) < bound.1 { return bound; }
return bound;
}
let mut boundary = boundary.clone(); let mut boundary = boundary.clone();
boundary.push(removed); boundary.push(removed);
@ -330,17 +225,14 @@ impl Surface for TriangleMesh {
} }
extern crate rand; extern crate rand;
use rand::seq::SliceRandom;
use rand::thread_rng; use rand::thread_rng;
use rand::seq::SliceRandom;
let mut points: Vec<&Point3f> = self.vertices.iter().collect(); let mut points: Vec<&Point3f> = self.vertices.iter().collect();
points.shuffle(&mut thread_rng()); points.shuffle(&mut thread_rng());
let (center, radius) = smallest_sphere(points, Vec::new()); let (center, radius) = smallest_sphere(points, Vec::new());
Some(Bound { Bound { center, radius: radius + 1e-3, bypass: false }
center,
radius: radius + 1e-3,
})
} }
} }

View file

@ -1,13 +1,18 @@
extern crate nalgebra as na;
use std::cmp::Ordering; 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::object::*;
use crate::util::*; use crate::types::*;
fn trace(ray: Ray, objects: &Vec<Object>) -> Option<(&Object, f64)> { fn trace(ray: Ray, objects: &Vec<Object>) -> Option<(&Object, f32)> {
objects objects.iter()
.iter() .filter_map(|obj| obj.intersect(ray)
.filter_map(|obj| obj.intersect(ray).map(|x| (obj, x))) .map(|x| (obj, x)))
.min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal)) .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap_or(Ordering::Equal))
} }
@ -15,10 +20,7 @@ fn light_point(objects: &Vec<Object>, obj: &Object, point: Point3f, light: &dyn
if light.check_shadow(point, objects) { if light.check_shadow(point, objects) {
let texture = obj.get_texture(point); let texture = obj.get_texture(point);
light.get_color(point) light.get_color(point) * (texture.albedo / PI) * light.intensity(point) * obj.normal(point).dot(&*light.direction(point))
* (texture.albedo / PI)
* light.intensity(point)
* obj.normal(point).dot(&*light.direction(point))
} else { } else {
// Point is in shadow // Point is in shadow
Color::black() Color::black()
@ -30,13 +32,8 @@ pub fn cast_ray(ray: Ray, scene: &Scene) -> Color {
let point = ray.project(dist); let point = ray.project(dist);
let surface_color = obj.get_texture(point).color; let surface_color = obj.get_texture(point).color;
scene scene.lights.iter()
.lights
.iter()
.map(|light| light_point(&scene.objects, obj, point, &**light)) .map(|light| light_point(&scene.objects, obj, point, &**light))
.fold(Color::black(), |acc, c| acc + c) .fold(Color::black(), |acc, c| acc + c) * surface_color
* surface_color } else { scene.background }
} else {
scene.background
}
} }

112
src/types.rs Normal file
View file

@ -0,0 +1,112 @@
extern crate nalgebra as na;
use std::ops::{Add, Mul};
use na::*;
use na::geometry::Point3;
pub type Point3f = Point3<f32>;
pub type Vector3f = Vector3<f32>;
pub type Unit3f = Unit<Vector3<f32>>;
#[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<f32> 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
}
}
}

View file

@ -1,122 +0,0 @@
use std::ops::{Add, Mul};
use nalgebra::geometry::Point3;
use nalgebra::*;
pub type Point3f = Point3<f64>;
pub type Vector3f = Vector3<f64>;
pub type Unit3f = Unit<Vector3<f64>>;
#[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<f64> 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,
}
}
}