Compare commits

...

10 commits

Author SHA1 Message Date
f0c8dae74c
refactor: use Option to bypass bounding sphere system 2024-10-16 15:48:42 -04:00
878f7c9ebb
style: stop renaming external crate 2024-10-16 15:33:34 -04:00
f33ee09b9d
refactor: rename types.rs to util.rs 2024-10-14 18:11:19 -04:00
763a4ff923
refactor: use 64-bit types instead of 32-bit
I don't know why I wasn't using 64 bit floats from the beginning,
honestly. I had weird priorities back then
2024-10-14 18:06:50 -04:00
9879184d47
feat: add nix build infrastructure 2024-10-14 17:55:47 -04:00
4e42390a82
docs: update author info 2024-10-14 17:54:58 -04:00
bijan-S
e2dd9f7e3a Fixed bounding sphere generation 2021-07-28 22:10:01 -04:00
bijan-S
cd94c7ad6f Fixed bug in plane intersection test 2021-05-05 21:48:22 -04:00
bijan2005
6d2a1407bf
Update LICENSE 2021-03-25 21:11:46 -04:00
bijan2005
736c1e416c
Add MIT License 2021-03-25 21:05:13 -04:00
21 changed files with 996 additions and 422 deletions

3
.envrc Normal file
View file

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

3
.gitignore vendored
View file

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

24
Cargo.lock generated
View file

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

View file

@ -1,11 +1,9 @@
[package]
name = "render"
version = "0.1.0"
authors = ["Bijan Sheibani"]
authors = ["Kiana 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"

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
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,4 +1,5 @@
# rust-render
A raytracing 3D renderer written in Rust.
### Checklist of features

285
flake.lock generated Normal file
View file

@ -0,0 +1,285 @@
{
"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
}

20
flake.nix Normal file
View file

@ -0,0 +1,20 @@
{
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 ];
};
}

18
module.nix Normal file
View file

@ -0,0 +1,18 @@
{
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 ];
});
};
}

2
rust-toolchain.toml Normal file
View file

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

View file

@ -1,87 +1,121 @@
extern crate nalgebra as na;
use nalgebra::geometry::{Point2, Point3};
use nalgebra::*;
use na::*;
use na::geometry::{Point2, Point3};
use crate::types::Ray;
use crate::util::Ray;
#[derive(Debug)]
pub struct Camera {
matrix: Isometry3<f32>, // The transformation that stores the
// position and orientation of the camera. (Not actually a matrix, but w/e)
matrix: Isometry3<f64>, // 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<f64>, // The size of the canvas within the world space.
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.
pub image_size: Vector2<u64>, // 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<f32>, dir: Vector3<f32>, up: Vector3<f32>,
focal_length: f32, aspect_ratio: f32, canvas_y: f32, image_y: u32) -> Self {
pub fn new_(
pos: Point3<f64>,
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);
Camera {
matrix: iso,
focal_length: focal_length,
canvas_size: Vector2::new(canvas_y * aspect_ratio, canvas_y),
image_size: Vector2::new((image_y as f32 * aspect_ratio) as u32, image_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),
}
}
// Constructs a new camera from a position and viewing direction
// (assuming the camera is oriented upright).
pub fn new(pos: Point3<f32>, dir: Vector3<f32>,
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 new(
pos: Point3<f64>,
dir: Vector3<f64>,
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<f32> { Point3::from(self.matrix.translation.vector) }
pub fn pos(&self) -> Point3<f64> {
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: u32, y: u32) -> Point3<f32> {
fn project(&self, x: u64, y: u64) -> Point3<f64> {
// convert point from raster coordinates to center-based coordinates
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 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 point: Point3<f32> = Point::from(pixelndc.coords.component_div(&self.image_size.map(|x| x as f32))
.component_mul(&self.canvas_size)
.fixed_resize(self.focal_length));
let point: Point3<f64> = Point::from(
pixelndc
.coords
.component_div(&self.image_size.map(|x| x as f64))
.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: u32, y: u32) -> Ray {
pub fn raycast(&self, x: u64, y: u64) -> Ray {
Ray::from_points(self.pos(), self.project(x, y))
}
}
#[cfg(test)]
mod tests {
use super::*;
fn round(point: Point3<f32>) -> Point3<f32> {
fn round(point: Point3<f64>) -> Point3<f64> {
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;
@ -91,10 +125,14 @@ 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;
@ -104,10 +142,14 @@ 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
@ -116,10 +158,14 @@ 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

View file

@ -1,18 +1,20 @@
extern crate nalgebra as na;
use std::time::Instant;
use std::fs::File;
use std::io::Write;
use std::time::Instant;
use na::*;
use nalgebra::*;
mod camera; use camera::*;
mod types; use types::*;
mod object; use object::*;
mod render; use render::*;
mod camera;
use camera::*;
mod util;
use util::*;
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<Color> = Vec::with_capacity((width * height) as usize);
@ -35,18 +37,22 @@ 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();

View file

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

View file

@ -1,30 +1,21 @@
extern crate nalgebra as na;
// use na::distance;
use na::geometry::Point3;
use nalgebra::geometry::Point3;
use crate::types::*;
use crate::util::*;
// A bounding sphere, used for
// intersection test optimization.
#[derive(Debug)]
pub struct Bound {
pub center: Point3f,
pub radius: f32,
// If true, then the bounding sphere is disabled.
pub bypass: bool
pub radius: f64,
}
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 } }
}

View file

@ -1,81 +1,105 @@
extern crate nalgebra as na;
use nalgebra::geometry::Point3;
use nalgebra::*;
use na::*;
use na::geometry::Point3;
use crate::types::*;
use super::{Surface, bound::*};
use super::{bound::*, Surface};
use crate::util::*;
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<dyn Fn(f32, f32) -> Texture> // Texture map.
// Input coordinates are defined in terms of the axes above.
texture: Box<dyn Fn(f64, f64) -> 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<F: 'static>(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self
where F: Fn(f32, f32) -> Texture
where
F: Fn(f64, f64) -> 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<F: 'static>(center: Point3f, x_axis: Vector3f, y_axis: Vector3f, texture: F) -> Self
where F: Fn(f32, f32) -> Texture
pub fn new_flip<F: 'static>(
center: Point3f,
x_axis: Vector3f,
y_axis: Vector3f,
texture: F,
) -> Self
where
F: Fn(f64, f64) -> 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(f32, f32) -> Texture) -> Self
{ Plane::new(Point3::origin(), Vector3::x(), Vector3::y(), texture) }
pub fn xy(texture: impl 'static + Fn(f64, f64) -> 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(f32, f32) -> Texture) -> Self
{ Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture) }
pub fn xz(texture: impl 'static + Fn(f64, f64) -> Texture) -> Self {
Plane::new(Point3::origin(), Vector3::x(), Vector3::z(), texture)
}
}
impl Surface for Plane {
fn intersect(&self, ray: Ray) -> Option<f32> {
fn intersect(&self, ray: Ray) -> Option<f64> {
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;
@ -87,7 +111,8 @@ impl Surface for Plane {
(*self.texture)(x, y)
}
// Planes are infinite, so no finite
// bounding sphere could possibly contain one.
fn bound(&self) -> Bound { Bound::bypass() }
// Planes are infinite, so no finite bounding sphere could possibly contain one
fn bound(&self) -> Option<Bound> {
None
}
}

View file

@ -1,34 +1,41 @@
extern crate nalgebra as na;
use nalgebra::*;
use na::*;
use crate::types::*;
use super::*;
use crate::util::*;
pub struct PointLight {
pub pos: Point3f,
pub color: Color,
pub intensity: f32
pub intensity: f64,
}
#[allow(dead_code)]
impl PointLight {
pub fn new(pos: Point3f, color: Color, intensity: f32) -> PointLight {
PointLight { pos, color, intensity }
pub fn new(pos: Point3f, color: Color, intensity: f64) -> PointLight {
PointLight {
pos,
color,
intensity,
}
}
}
impl Light for PointLight {
fn check_shadow(&self, point: Point3f, objects: &Vec<Object>) -> 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) -> f32 { self.intensity }
fn intensity(&self, _point: Point3f) -> f64 {
self.intensity
}
fn direction(&self, point: Point3f) -> Unit3f {
Unit::new_normalize(self.pos - point)
@ -42,7 +49,13 @@ 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]));

View file

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

View file

@ -1,12 +1,10 @@
extern crate nalgebra as na;
use std::cmp::Ordering;
use na::*;
use na::geometry::Point3;
use nalgebra::geometry::Point3;
use nalgebra::*;
use crate::types::*;
use super::{Surface, bound::*};
use super::{bound::*, Surface};
use crate::util::*;
pub struct Triangle {
pub v1: usize, // Handles to 3 vertices.
@ -14,35 +12,45 @@ pub struct Triangle {
pub v3: usize,
normal: Unit3f, // Precalculated normal vector.
area: f32, // Precalculated area for barycentric calculations.
area: f64, // Precalculated area for barycentric calculations.
texture: Box<dyn Fn(f32, f32, f32) -> Texture> // Texture map.
// Uses barycentric coordinates as input.
texture: Box<dyn Fn(f64, f64, f64) -> Texture>, // Texture map.
// Uses barycentric coordinates as input.
}
pub struct TriangleMesh {
pub vertices: Vec<Point3f>,
pub triangles: Vec<Triangle>
pub triangles: Vec<Triangle>,
}
fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f32 {
let prlg_area: f32 = (b - a).cross(&(c - a)).norm();
fn tri_area(a: &Point3f, b: &Point3f, c: &Point3f) -> f64 {
let prlg_area: f64 = (b - a).cross(&(c - a)).norm();
prlg_area / 2.0
}
impl Triangle {
fn vertex1<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f { &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 vertex1<'a>(&self, vertices: &'a Vec<Point3f>) -> &'a Point3f {
&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]
}
// Conversion of barycentric coordinates to
// a point on the triangle.
fn from_bary(&self, vertices: &Vec<Point3f>, t: f32, u: f32, v: f32) -> Point3f {
Point::from(t * self.vertex1(vertices).coords + u * self.vertex2(vertices).coords + v * self.vertex3(vertices).coords)
fn from_bary(&self, vertices: &Vec<Point3f>, t: f64, u: f64, v: f64) -> 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<Point3f>, point: Point3f) -> (f32, f32, f32) {
fn to_bary(&self, vertices: &Vec<Point3f>, point: Point3f) -> (f64, f64, f64) {
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;
@ -50,32 +58,39 @@ impl Triangle {
(t, u, v)
}
fn intersect_(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<(f32, f32, f32)> {
fn intersect_(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<(f64, f64, f64)> {
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<Point3f>, ray: Ray) -> Option<f32> {
self.intersect_(vertices, ray).map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v)))
fn intersect(&self, vertices: &Vec<Point3f>, ray: Ray) -> Option<f64> {
self.intersect_(vertices, ray)
.map(|(t, u, v)| distance(&ray.origin, &self.from_bary(vertices, t, u, v)))
}
fn get_texture(&self, vertices: &Vec<Point3f>, point: Point3f) -> Texture {
@ -86,45 +101,81 @@ impl Triangle {
#[allow(dead_code)]
impl TriangleMesh {
pub fn new(vertices: Vec<Point3f>, tris: Vec<(usize, usize, usize, Box<dyn Fn(f32, f32, f32) -> 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
}
}
pub fn new_solid(vertices: Vec<Point3f>, 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(
vertices: Vec<Point3f>,
tris: Vec<(usize, usize, usize, Box<dyn Fn(f64, f64, f64) -> 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
triangles,
}
}
pub fn singleton<F: 'static>(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 new_solid(
vertices: Vec<Point3f>,
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,
}
}
pub fn singleton_solid(vertex1: Point3f, vertex2: Point3f, vertex3: Point3f, texture: Texture) -> Self
{ TriangleMesh::singleton(vertex1, vertex2, vertex3, move |_, _, _| texture) }
pub fn singleton<F: 'static>(
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_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));
@ -139,15 +190,17 @@ 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<f32> {
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<f64> {
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 {
@ -159,63 +212,115 @@ impl Surface for TriangleMesh {
}
// Uses Welzl's algorithm to solve the bounding sphere problem
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());
let radius = to_center.norm();
(point1 + to_center, radius)
}
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();
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(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, &center);
(center, radius)
}
fn smallest_sphere(points: Vec<&Point3f>, boundary: Vec<&Point3f>) -> (Point3f, f32) {
if points.len() == 0 || boundary.len() == 4 {
fn bound(&self) -> Option<Bound> {
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()) },
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);
smallest_sphere_plane(points, boundary)
}
}
fn triangle_sphere(point1: &Point3f, point2: &Point3f, point3: &Point3f) -> (Point3f, f64) {
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());
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(),
]);
let a = matrix.determinant() * 2.0;
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();
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();
let center = Point3::new(center_x / a, center_y / a, center_z / a);
let radius = distance(point1, &center);
(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) {
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())
}
3 => triangle_sphere(boundary[0], boundary[1], boundary[2]),
4 => tetrahedron_sphere(boundary[0], boundary[1], boundary[2], boundary[3]),
_ => 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);
@ -225,14 +330,17 @@ impl Surface for TriangleMesh {
}
extern crate rand;
use rand::thread_rng;
use rand::seq::SliceRandom;
use rand::thread_rng;
let mut points: Vec<&Point3f> = self.vertices.iter().collect();
points.shuffle(&mut thread_rng());
let (center, radius) = smallest_sphere(points, Vec::new());
Bound { center, radius: radius + 1e-3, bypass: false }
Some(Bound {
center,
radius: radius + 1e-3,
})
}
}

View file

@ -1,18 +1,13 @@
extern crate nalgebra as na;
use std::cmp::Ordering;
use std::f32::consts::PI;
use na::*;
use na::geometry::Point3;
use std::f64::consts::PI;
use crate::object::*;
use crate::types::*;
use crate::util::*;
fn trace(ray: Ray, objects: &Vec<Object>) -> Option<(&Object, f32)> {
objects.iter()
.filter_map(|obj| obj.intersect(ray)
.map(|x| (obj, x)))
fn trace(ray: Ray, objects: &Vec<Object>) -> Option<(&Object, f64)> {
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))
}
@ -20,7 +15,10 @@ fn light_point(objects: &Vec<Object>, 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()
@ -32,8 +30,13 @@ 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
}
}

View file

@ -1,112 +0,0 @@
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
}
}
}

122
src/util.rs Normal file
View file

@ -0,0 +1,122 @@
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,
}
}
}