Compare commits

..

No commits in common. "main" and "master" have entirely different histories.
main ... master

554 changed files with 737987 additions and 2 deletions

1
.envrc Normal file
View file

@ -0,0 +1 @@
use flake

1
.gitattributes vendored Normal file
View file

@ -0,0 +1 @@
assignment-2*/ext/* linguist-vendored

4
.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
.direnv
.pijul
/target

2
.ignore Normal file
View file

@ -0,0 +1,2 @@
.git
.DS_Store

1288
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

20
Cargo.toml Normal file
View file

@ -0,0 +1,20 @@
[workspace]
members = [
"assignment-0",
"assignment-1a",
"assignment-1b",
"assignment-1c",
"assignment-1d",
"assignment-2a-rust",
]
# For profiling with flamegraphs
[profile.release]
debug = true
# Optimize for size when creating handin
[profile.release-handin]
inherits = "release"
strip = true
lto = true

View file

@ -1,2 +0,0 @@
# csci5607

4
assignment-0/.gitignore vendored Normal file
View file

@ -0,0 +1,4 @@
/target
input
out.ppm
handin.tar.gz

View file

@ -0,0 +1,77 @@
# Assignment 0: Working with ASCII images
In this assignment, you are asked to write a program that creates an image in
ASCII PPM format.
Your program should accept, as a command line argument, the name of a file
containing an "image description" (consisting of one line of text specifying the
image height and image width, optionally followed other lines of text specifying
any additional parameters your program might use), and it should generate a
valid ASCII PPM file as output. The name of the output file should end with the
suffix .ppm. Your program should be capable of accepting different input files
and producing different output files without needing to be recompiled.
You are free to use whatever means you like to define the contents of the output
image. If you have time and are so inclined, you can earn extra credit by doing
something creative. Possibilities include: hardcoding an interesting pattern,
generating a [procedural texture][1], generating an image of the [Mandelbrot
set][2], or reading an image from a file and applying an interesting [filter][3]
or function such as [line integral convolution][4]. Whatever approach you
choose, the code you turn in **must** be your own.
[1]: https://en.wikipedia.org/wiki/Procedural_texture
[2]: https://en.wikipedia.org/wiki/Mandelbrot_set
[3]: https://en.wikipedia.org/wiki/Kernel_(image_processing)
[4]: https://en.wikipedia.org/wiki/Line_integral_convolution
### Detailed instructions
1. Please use the following syntax to define the image size in your input file:
imsize width height
where imsize is a keyword and width and height are integers.
2. Please use the following syntax to define your output image:
P3
# any comments you want to include
width height
255
r(0,0) g(0,0) b(0,0) r(1,0) g(1,0) b(1,0) r(2,0) g(2,0) b(2,0) ...
Please note that in an ASCII PPM file, each pixel color is defined as an (r, g,
b) triple, and the provided values are used to fill the image in row major
order. Specifically, the first three entries represent the red, green and blue
components of the color of the upper leftmost pixel in the image, the second
three entries represent the color of the adjacent pixel to the right, and so on.
Your program will need to specify exactly one color triple for each pixel in
your image. Try to keep the line length under 80 characters, or approximately
4-5 pixels per line. Its also okay to put each color component on its own line
too. Do not insert any other characters such as commas, etc. between any of the
color values.
You can use the free program [GIMP] to view your image, to verify that it looks
the way you intend it to. GIMP is a general-purpose image editing tool that can
also be used to convert images from multiple other formats into ASCII PPM format
(and vice versa).
[gimp]: http://www.gimp.org
### What you should turn in
- a readme file that describes your what your program does
- all of your source code, clearly commented
- at least one ascii PPM image created by your program
We will plan to share the PPM images in class. If you would prefer that we not
show your image, please let us know.
We will use this rubric: grading-criteria-0.pdf when grading your program. You
are advised to check the rubric before beginning the assignment to fully
understand how credit will be awarded.

430
assignment-0/Cargo.lock generated Normal file
View file

@ -0,0 +1,430 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aho-corasick"
version = "0.7.20"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cc936419f96fa211c1b9166887b38e5e40b19958e5b895be7c1f93adec7071ac"
dependencies = [
"memchr",
]
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "assignment-0"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"lazy_static",
"rand",
"regex",
]
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "cc"
version = "1.0.78"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a20104e2335ce8a659d6dd92a51a767a0c062599c73b343fd152cb401e828c3d"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ec7a4128863c188deefe750ac1d1dfe66c236909f845af04beed823638dc1b2"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "io-lifetimes"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "regex"
version = "1.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48aaa5748ba571fb95cd2c85c09f629215d3a6ece942baa100950af03a34f733"
dependencies = [
"aho-corasick",
"memchr",
"regex-syntax",
]
[[package]]
name = "regex-syntax"
version = "0.6.28"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "456c603be3e8d448b072f410900c09faf164fbce2d480456f50eea6e25f9c848"
[[package]]
name = "rustix"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

12
assignment-0/Cargo.toml Normal file
View file

@ -0,0 +1,12 @@
[package]
authors = ["Michael Zhang <zhan4854@umn.edu>"]
name = "assignment-0"
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.68"
clap = { version = "4.1.1", features = ["derive", "cargo"] }
lazy_static = "1.4.0"
rand = "0.8.5"
regex = "1.7.1"

7
assignment-0/Makefile Normal file
View file

@ -0,0 +1,7 @@
.PHONY: clean
handin.tar.gz:
tar -czvf $@ target/release/assignment-0 Cargo.* input out.ppm README.md src
clean:
rm -f handin.tar.gz

31
assignment-0/README.md Normal file
View file

@ -0,0 +1,31 @@
Assignment 0: Generating turbulence with Rust!
===
This program generates PPM images.
Using the program
---
The binary provided has been built for Ubuntu, and has been tested on CSELabs.
Run `assignment-0 --help` to find out about all the options for using this
program.
Compiling the source
---
This project is a standard Rust program, and can be built with just a Rust
compiler. The version of Rust used is the current stable at the time of writing,
1.66.
Build the program using:
```
cargo build --release
```
The binary can be found in the `target` directory.
Contact
---
Author: Michael Zhang

34
assignment-0/src/image.rs Normal file
View file

@ -0,0 +1,34 @@
use std::io::{Result, Write};
/// A 24-bit pixel represented by a red, green, and blue value.
pub struct Pixel(pub u8, pub u8, pub u8);
/// A representation of an image
pub struct Image {
/// Width in pixels
pub(crate) width: usize,
/// Height in pixels
pub(crate) height: usize,
/// Pixel data in row-major form.
pub(crate) data: Vec<Pixel>,
}
impl Image {
/// Write the image in PPM format to a file.
pub fn write(&self, mut w: impl Write) -> Result<()> {
// Header
let header = format!("P3 {} {} 255\n", self.width, self.height);
w.write_all(header.as_bytes())?;
// Pixel data
for pixel in self.data.iter() {
let Pixel(red, green, blue) = pixel;
let pixel = format!("{red} {green} {blue}\n");
w.write_all(pixel.as_bytes())?;
}
Ok(())
}
}

111
assignment-0/src/main.rs Normal file
View file

@ -0,0 +1,111 @@
use std::{
fs::File,
io::Read,
path::{Path, PathBuf},
};
use anyhow::{bail, Context, Result};
use clap::{Parser, ValueEnum};
use lazy_static::lazy_static;
use regex::Regex;
use turbulence::generate_turbulence;
use crate::value_noise::generate_noise;
mod image;
mod turbulence;
mod value_noise;
mod vec2;
/// Noise image generator.
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Opt {
/// Path to the input file to use.
///
/// The input file should follow this format:
///
/// imsize [width] [height]
///
/// Where `imsize' is a keyword, and `width' and `height' are integer values
/// denoting the desired size of the image to be generated.
#[clap()]
input_path: PathBuf,
/// The algorithm to use for pattern generation.
#[clap(long = "algorithm", default_value = "turbulence")]
algorithm: Algorithm,
/// The output image path.
#[clap(short = 'o', long = "out", default_value = "out.ppm")]
output_path: PathBuf,
}
#[derive(ValueEnum, Clone)]
enum Algorithm {
/// Generates noise, which is linearly interpolated randomness.
Noise,
/// Fractal sum made with signed noise, used to resemble smoke.
Turbulence,
}
fn main() -> Result<()> {
let opt = Opt::parse();
let (width, height) = parse_input_file(&opt.input_path)?;
// We will use the default thread-local rng. Future work could include
// allowing the rng to be seedable from the command line
let rng = rand::thread_rng();
// Pick the algorithm to use based on the option passed via command line
let ppm = match opt.algorithm {
Algorithm::Noise => generate_noise(width, height, rng),
Algorithm::Turbulence => generate_turbulence(width, height, rng),
};
// Write the PPM image to the output file specified
{
let file = File::create(&opt.output_path).with_context(|| {
format!("Could not create file {:?}.", opt.output_path)
})?;
ppm.write(file).with_context(|| {
format!("Could not write to file {:?}.", opt.output_path)
})?;
}
Ok(())
}
lazy_static! {
static ref INPUT_PATTERN: Regex =
Regex::new(r"imsize\s+(?P<width>\d+)\s+(?P<height>\d+)")
.expect("Failed to compile regex.");
}
/// Parse the input file, returning the target image dimensions
fn parse_input_file(path: impl AsRef<Path>) -> Result<(usize, usize)> {
let path = path.as_ref();
// Read the contents from the file
let contents = {
let mut contents = String::new();
let mut file = File::open(path)?;
file.read_to_string(&mut contents)?;
contents
};
// Parse the contents using regex
let matches = match INPUT_PATTERN.captures(&contents) {
Some(v) => v,
None => bail!("Invalid input file syntax."),
};
// Extract the parsed captures and convert to int
let width = matches.name("width").unwrap().as_str().parse()?;
let height = matches.name("height").unwrap().as_str().parse()?;
Ok((width, height))
}

View file

@ -0,0 +1,52 @@
// Follows this algorithm:
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/simple-pattern-examples.html
use rand::RngCore;
use crate::image::{Image, Pixel};
use crate::value_noise::ValueNoise;
use crate::vec2::Vec2;
pub fn generate_turbulence(
width: usize,
height: usize,
rng: impl RngCore,
) -> Image {
let mut noise_map = vec![0.0; width * height];
let noise = ValueNoise::new(rng);
let frequency = 0.02;
let frequency_mult = 1.8;
let amplitude_mult = 0.35;
let num_layers = 5;
let mut max_noise_val = 0.0f64;
for j in 0..height {
for i in 0..width {
let mut noise_point = Vec2::new(i as f64, j as f64) * frequency;
let mut amplitude = 1.0;
for _ in 0..num_layers {
noise_map[j * width + i] =
(2.0 * noise.eval(noise_point) - 1.0).abs() * amplitude;
noise_point = noise_point * frequency_mult;
amplitude *= amplitude_mult;
}
max_noise_val = max_noise_val.max(noise_map[j * width + i]);
}
}
let data = noise_map
.into_iter()
.map(|f| {
let v = (f / max_noise_val * 192.0 + 32.0).floor() as u8;
Pixel(v, v, v)
})
.collect();
Image {
width,
height,
data,
}
}

View file

@ -0,0 +1,90 @@
// Follows this algorithm:
// https://www.scratchapixel.com/lessons/procedural-generation-virtual-worlds/procedural-patterns-noise-part-1/creating-simple-2D-noise.html
use std::mem;
use rand::{Rng, RngCore};
use crate::image::{Pixel, Image};
use crate::vec2::Vec2;
const MAX_TABLE_SIZE: usize = 256;
const MAX_TABLE_SIZE_MASK: usize = MAX_TABLE_SIZE - 1;
pub fn generate_noise(width: usize, height: usize, rng: impl RngCore) -> Image {
let mut noise_map = vec![0.0; width * height];
let noise = ValueNoise::new(rng);
let frequency = 0.05;
for j in 0..height {
for i in 0..width {
let vec = Vec2::new(i as f64, j as f64);
noise_map[j * width + i] = noise.eval(vec * frequency);
}
}
let data = noise_map
.into_iter()
.map(|f| {
let v = (f * 256.0).floor() as u8;
Pixel(v, v, v)
})
.collect();
Image {
width,
height,
data,
}
}
pub struct ValueNoise {
r: [f64; MAX_TABLE_SIZE * MAX_TABLE_SIZE],
}
impl ValueNoise {
pub fn new(mut rng: impl RngCore) -> Self {
let mut r: [f64; MAX_TABLE_SIZE * MAX_TABLE_SIZE] =
unsafe { mem::zeroed() };
for k in 0..MAX_TABLE_SIZE * MAX_TABLE_SIZE {
r[k] = rng.gen_range(0.0..1.0);
}
ValueNoise { r }
}
pub fn eval(&self, point: Vec2) -> f64 {
let xi = point.x.floor();
let yi = point.y.floor();
let tx = point.x - xi;
let ty = point.y - yi;
let xi = xi as usize;
let yi = yi as usize;
let rx0 = xi & MAX_TABLE_SIZE_MASK;
let rx1 = (rx0 + 1) & MAX_TABLE_SIZE_MASK;
let ry0 = yi & MAX_TABLE_SIZE_MASK;
let ry1 = (ry0 + 1) & MAX_TABLE_SIZE_MASK;
let c00 = self.r[ry0 * MAX_TABLE_SIZE_MASK + rx0];
let c10 = self.r[ry0 * MAX_TABLE_SIZE_MASK + rx1];
let c01 = self.r[ry1 * MAX_TABLE_SIZE_MASK + rx0];
let c11 = self.r[ry1 * MAX_TABLE_SIZE_MASK + rx1];
let sx = smooth_step(tx);
let sy = smooth_step(ty);
let nx0 = lerp(c00, c10, sx);
let nx1 = lerp(c01, c11, sx);
lerp(nx0, nx1, sy)
}
}
fn smooth_step(t: f64) -> f64 {
t * t * (3.0 - 2.0 * t)
}
fn lerp(lo: f64, hi: f64, t: f64) -> f64 {
lo * (1.0 - t) + hi * t
}

25
assignment-0/src/vec2.rs Normal file
View file

@ -0,0 +1,25 @@
use std::ops::Mul;
/// A generic 2-dimensional point class.
#[derive(Copy, Clone)]
pub struct Vec2<T = f64> {
pub x: T,
pub y: T,
}
impl<T> Vec2<T> {
pub fn new(x: T, y: T) -> Self {
Self { x, y }
}
}
impl<S: Copy, T: Mul<S>> Mul<S> for Vec2<T> {
type Output = Vec2<T::Output>;
fn mul(self, rhs: S) -> Self::Output {
Vec2 {
x: self.x * rhs,
y: self.y * rhs,
}
}
}

6
assignment-1a/.gitignore vendored Normal file
View file

@ -0,0 +1,6 @@
/target
/assignment-1
/examples/*.png
*.ppm
*.zip
*.pdf

183
assignment-1a/ASSIGNMENT.md Normal file
View file

@ -0,0 +1,183 @@
# Assignment 1a: Getting Started with Ray Casting
In this assignment, you are asked to begin writing a basic ray casting program.
This program should:
1. read a simple scene description from a file that is provided as a command
line argument
2. define an array of pixels that will hold a computer-generated image of the
scene
3. use ray casting to determine the appropriate color to store in each pixel of
the output image
4. write the rendered image to an output file in ascii PPM format
As with assignment 0, the output file name should match the input file name
except for the suffix. You can also earn extra credit by enabling your program
to render a parallel projection and/or to render not only spheres but also
cylinders.
You are asked to run your program on multiple different input files, featuring a
diverse range of different input parameter settings. One of the objectives of
this assignment is to help you to develop an intuitive understanding of how each
of the various input settings affects the appearance of the rendered scene.
Please be sure to specifically consider these three questions:
- How does the direction of the 'up' vector affect the rendered view of a scene?
You should specifically consider tipping the 'up' vector from side to side and
forward to back. You will want to define scenes with multiple objects,
asymmetrically arranged, to explore this question.
- How do changes in the field of view settings affect the contents of your
rendered images?
- How do various modifications of the viewing parameters affect the amount of
perspective distortion apparent in your image?
In subsequent assignments you will be asked to extend the code you write for
this assignment to incorporate additional effects like illumination, shadows,
specular reflections and transparency, with these latter effects being achieved
by recursive ray tracing. Be sure that your code is structured with these
extensions in mind. In particular, you are advised to write simple, modular code
following the example described in class.
Please be sure to check the grading criteria to ensure that all requirements are
understood.
### Detailed instructions
1. Your program should read the following information from an input scene
description file (the syntax is shown below each item; entries in italics
are variables, entries in bold are keywords):
- The view origin, also variously referred to as the 'eye position', 'camera
position' or 'center of projection' (a 3D point in space)
eye eyex eyey eyez
- The viewing direction (a 3D vector)
viewdir vdirx vdiry vdirz
- The 'up' direction (a 3D vector)
updir upx upy upz
- The horizontal field of view (in degrees, please)
hfov fovh
- The size of the output image (in pixel units)
imsize width height
- The 'background' color (using r, g, b components defined on a scale from
0-1)
bkgcolor r g b
- A 'material' color (in terms of r, g, b components defined on a scale from
0-1). The material color should be treated as a state variable, meaning
that all subsequently-defined objects should use the immediately-preceding
material color
mtlcolor r g b
- A sequence of one or more objects. For this assignment, your program is
only required to be able to handle spheres. In subsequent assignments you
will be asked to extend your code to handle triangles, so you will want to
write your code with this future extension in mind. A sphere should be
defined by the coordinates of its center point (a 3D point in space) and
radius.
sphere cx cy cz r
- If you would like to attempt the extra credit portion of this assignment,
please use this format to define a parallel projection
projection parallel
- If you would like to attempt the extra credit portion of this assignment,
please use this format to define a finite cylinder (without endcaps) that
is centered at the point (cx, cy, cz) and oriented in the direction (dirx,
diry, dirz).
cylinder cx cy cz dirx diry dirz radius length
2. Define an array of sufficient size to store the color values of your image.
3. Using knowledge of the view origin, viewing direction, up direction,
horizontal field of view and desired image aspect ratio, define an
appropriate “viewing window” in world coordinate space, and a 1-1 mapping
between points within this viewing window and pixel locations in your output
image.
4. For each pixel in the output image:
- Define the equation of the ray that begins at the view origin and passes
through the corresponding 3D point in the viewing window
- Then, for each object in the scene:
- Determine whether the current viewing ray intersects that object, and if
so at what point or points
- If there are one or more ray/object intersection points that are 'in
front of' the view origin with respect to the positive viewing
direction, determine which of them is closest to the view origin,
remembering that ray/object intersection points that are 'behind' the
view origin, with respect to the viewing direction, should be ignored
- Determine the color of the object at the closest forward ray/object
intersection point, if there is one, and return that value to be stored
at the appropriate location in your image array
- If no ray/object intersection is found, return the background color
5. After all of the rays have been cast and a color has been determined for
each pixel in the output image, write the final image to an output file,
using the ascii PPM format.
You are strongly encouraged to begin with a very simple scene description,
consisting for example of a single sphere located directly in front of the eye
in the direction of view. You are also advised to begin by using a very small
image size, to expedite the debugging process.
To explore the effects of various scene parameters, you are advised to start by
varying the values of just one viewing parameter at a time, such as: the eye
position, the viewing direction, the direction of the 'up' vector, the vertical
field of view, the image aspect ratio, etc. Once you understand the effect of
each parameter in isolation, you should experiment with combinations of changes,
such as: co-varying the field of view and the proximity of the eye to the
objects in your scene. Your ability to appreciate the effects of different view
settings will be improved if you use a relatively complex but intuitively
organized scene, with some asymmetric features and not too much empty space. One
example is: spheres centered at each of the 8 corners of a cube, using different
colors for each corner, or cylinders arranged along each of the 12 edges of a
cube.
### What you should turn in
- All of your source code, clearly commented, plus a ~~readme file with
compiling instructions or a~~ Makefile or CMake file that the TA can use to
compile your code.
- One "showcase" image produced by your program, that I can share with the rest
of the class. To save time for the TA, we would be grateful if you could
provide this image in a format that is supported for direct display in Google
Slides or Powerpoint.
- A 1-3 page writeup (including pictures) in which you discuss your observations
on how the key viewing parameters affect the appearance of the rendered scene.
Please incorporate sufficient images produced by your ray casting program to
illustrate and explain your findings. In your writeup, be sure to specifically
address each of these three points:
- How does the apparent rotation of the scene with respect to the viewpoint
change with changes in the direction of the 'up' vector?
- How do changes in the field of view settings affect the appearance of the
scene in your rendered image?
- How can the viewing parameters (e.g. the camera location, field of view
settings, …) be adjusted to achieve a less exaggerated vs more exaggerated
amount of apparent perspective distortion in your image?
- Please submit all of the above as a single zip file, containing appropriate
subfolders, using the naming convention hw1a.firstname.lastname.zip. Please
do not use tar or rar or any other alternative compression mode that could
complicate the grading process for the TA.
---
Two sample input files with their corresponding output images are provided in
the Files directory of this Canvas website. Please feel free to use these
examples to partially check the functionality of your program. We will be
testing your code on additional input files.

637
assignment-1a/Cargo.lock generated Normal file
View file

@ -0,0 +1,637 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "assignment-1"
version = "0.1.0"
dependencies = [
"anyhow",
"clap",
"nalgebra",
"num",
"ordered-float",
"rayon",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "io-lifetimes"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "matrixmultiply"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
dependencies = [
"rawpointer",
]
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "nalgebra"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6515c882ebfddccaa73ead7320ca28036c4bc84c9bcca3cc0cbba8efe89223a"
dependencies = [
"approx",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d232c68884c0c99810a5a4d333ef7e47689cfd0edc85efc9e54e1e6bf5212766"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-complex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "ordered-float"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf"
dependencies = [
"num-traits",
]
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "paste"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "rustix"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "safe_arch"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529"
dependencies = [
"bytemuck",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]]
name = "simba"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50582927ed6f77e4ac020c057f37a268fc6aebc29225050365aacbb9deeeddc4"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
"wide",
]
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wide"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae41ecad2489a1655c8ef8489444b0b113c0a0c795944a3572a0931cf7d2525c"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

13
assignment-1a/Cargo.toml Normal file
View file

@ -0,0 +1,13 @@
[package]
name = "assignment-1"
authors = ["Michael Zhang <zhan4854@umn.edu>"]
version = "0.1.0"
edition = "2021"
[dependencies]
anyhow = "1.0.68"
clap = { version = "4.1.4", features = ["derive"] }
nalgebra = "0.32.1"
num = { version = "0.4.0", features = ["serde"] }
ordered-float = "3.4.0"
rayon = "1.6.1"

43
assignment-1a/Makefile Normal file
View file

@ -0,0 +1,43 @@
.PHONY: all clean
DOCKER := docker
ZIP := zip
PANDOC := pandoc
CONVERT := convert
HANDIN := hw1a.michael.zhang.zip
BINARY := ./assignment-1
WRITEUP := writeup.pdf
SOURCES := $(shell find -name "*.rs")
EXAMPLES := $(shell find examples -name "*.txt")
EXAMPLES_PPM := $(patsubst %.txt,%.ppm,$(EXAMPLES))
EXAMPLES_PNG := $(patsubst %.txt,%.png,$(EXAMPLES))
all: $(HANDIN)
$(BINARY): $(SOURCES)
$(DOCKER) run \
--rm \
-v "$(shell pwd)":/usr/src/myapp \
-v cargo-registry:/usr/local/cargo \
--user "$(shell id -u)":"$(shell id -g)" \
-w /usr/src/myapp \
rust \
cargo build --release
mv target/release/assignment-1 $@
$(HANDIN): $(BINARY) $(WRITEUP) Makefile Cargo.toml Cargo.lock README.md $(EXAMPLES_PNG) $(EXAMPLES_PPM)
$(ZIP) -r $@ src examples $^
examples/%.ppm: examples/%.txt
cargo run -- -o $@ $<
examples/%.png: examples/%.ppm
convert $< $@
writeup.pdf: writeup.md $(EXAMPLES_PNG)
$(PANDOC) -o $@ $<
clean:
rm -f $(HANDIN) $(BINARY) $(WRITEUP) $(EXAMPLES_PPM) $(EXAMPLES_PNG)

25
assignment-1a/README.md Normal file
View file

@ -0,0 +1,25 @@
# Raycaster
## Bundle contents
Writeup is located at `/writeup.pdf`.
The binary can be found at `/assignment-1`. Run `./assignment-1 --help` to see
how to use it. The binary has been built using the Rust Docker image, which
should have an environment similar to CSELabs. If there is trouble running the
binary, try building from source, as documented below.
Examples are found in the `examples` directory. The text files are the input
sources, and the ppm files are the corresponding outputs. They have been
generated by running this program. For convenience, pngs have also been provided
using imagemagick.
## Building from source
The Makefile currently uses Docker to produce a more consistent build. If you
have a Rust+Cargo toolchain installed locally, it's also possible to build the
source using just:
cargo build --release
The binary will be found in `target/release`.

BIN
assignment-1a/doc/fov.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 38 KiB

BIN
assignment-1a/doc/map.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 32 KiB

BIN
assignment-1a/doc/rot.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,21 @@
imsize 640 480
eye 0 0 15
viewdir 0 0 -1
hfov 60
updir 0 1 0
bkgcolor 0.1 0.1 0.1
mtlcolor 0 0.5 0.5
sphere -1 -2 -5 2
sphere 3 -5 -1 0.5
mtlcolor 0.5 0.5 1
sphere 1 2 -3 3
sphere -6 3 -4 1
mtlcolor 0.5 0 0.5
sphere 5 5 -1 1
sphere -6 -4 -8 7
mtlcolor 0.5 1 0.5
cylinder 5 1 -2 1 -2 1 1 2

View file

@ -0,0 +1,21 @@
imsize 640 480
eye 0 0 15
viewdir 0 0 -1
hfov 30
updir 0 1 0
bkgcolor 0.1 0.1 0.1
mtlcolor 0 0.5 0.5
sphere -1 -2 -5 2
sphere 3 -5 -1 0.5
mtlcolor 0.5 0.5 1
sphere 1 2 -3 3
sphere -6 3 -4 1
mtlcolor 0.5 0 0.5
sphere 5 5 -1 1
sphere -6 -4 -8 7
mtlcolor 0.5 1 0.5
cylinder 5 1 -2 1 -2 1 1 2

View file

@ -0,0 +1,21 @@
imsize 640 480
eye 0 0 15
viewdir 0 0 -1
hfov 60
updir 0 1 0
bkgcolor 0.1 0.1 0.1
mtlcolor 0 0.5 0.5
sphere -1 -2 -5 2
sphere 3 -5 -1 0.5
mtlcolor 0.5 0.5 1
sphere 1 2 -3 3
sphere -6 3 -4 1
mtlcolor 0.5 0 0.5
sphere 5 5 -1 1
sphere -6 -4 -8 7
mtlcolor 0.5 1 0.5
cylinder 5 1 -2 1 -2 1 1 2

View file

@ -0,0 +1,8 @@
imsize 256 256
eye 0 0 0
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.1 0.1 0.1
mtlcolor 0 0 1
sphere 0 0 -5 2

View file

@ -0,0 +1,10 @@
imsize 512 256
eye 0 0 0
viewdir 0 0 -1
hfov 130
updir 0 1 1
bkgcolor 0.1 0.1 0.1
mtlcolor 0 1 0
sphere -0.5 -1 -8 3
mtlcolor 1 0 0
sphere 3 1 -3 1

View file

@ -0,0 +1,9 @@
imsize 640 480
eye 0 0 8
viewdir 0 0 -1
hfov 60
updir 0 1 0
bkgcolor 1 1 1
mtlcolor 0.5 1 0.5
cylinder 0 0 -4 1 0 0 1 8

View file

@ -0,0 +1,9 @@
imsize 640 480
eye 0 0 8
viewdir 0 0 -1
hfov 60
updir 1 1 0
bkgcolor 1 1 1
mtlcolor 0.5 1 0.5
cylinder 0 0 -4 1 0 0 1 8

View file

@ -0,0 +1,55 @@
use std::io::{Result, Write};
/// A 24-bit pixel represented by a red, green, and blue value.
#[derive(Clone, Copy, Default, Debug)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Color {
pub fn new(r: u8, g: u8, b: u8) -> Self {
Color {
red: r,
green: g,
blue: b,
}
}
pub fn from_01_float(r: f64, g: f64, b: f64) -> Self {
Color::new((r * 256.0) as u8, (g * 256.0) as u8, (b * 256.0) as u8)
}
}
/// A representation of an image
pub struct Image {
/// Width in pixels
pub(crate) width: usize,
/// Height in pixels
pub(crate) height: usize,
/// Pixel data in row-major form.
pub(crate) data: Vec<Color>,
}
impl Image {
/// Write the image in PPM format to a file.
pub fn write(&self, mut w: impl Write) -> Result<()> {
// Header
let header = format!("P3 {} {} 255\n", self.width, self.height);
w.write_all(header.as_bytes())?;
// Pixel data
assert_eq!(self.data.len(), self.width * self.height);
for pixel in self.data.iter() {
let Color { red, green, blue } = pixel;
let pixel = format!("{red} {green} {blue}\n");
w.write_all(pixel.as_bytes())?;
}
Ok(())
}
}

View file

@ -0,0 +1,115 @@
use std::{fs::File, io::Read, path::Path};
use anyhow::Result;
use nalgebra::Vector3;
use crate::{
image::Color,
scene::{
cylinder::Cylinder,
data::{Object, Scene},
sphere::Sphere,
},
};
/// Parse the input file into a scene
pub fn parse_input_file(path: impl AsRef<Path>) -> Result<Scene> {
let contents = {
let mut contents = String::new();
let mut file = File::open(path.as_ref())?;
file.read_to_string(&mut contents)?;
contents
};
let mut scene = Scene::default();
let mut material_color = None;
for line in contents.lines() {
let mut parts = line.split_whitespace();
let keyword = match parts.next() {
Some(v) => v,
None => continue,
};
if keyword == "imsize" {
let parts = parts
.map(|s| s.parse::<usize>().map_err(|e| e.into()))
.collect::<Result<Vec<_>>>()?;
if let [width, height] = parts[..] {
scene.image_width = width;
scene.image_height = height;
}
} else if keyword == "projection" {
if let Some("parallel") = parts.next() {
scene.parallel_projection = true;
}
}
// Do float parsing instead
else {
let parts = parts
.map(|s| s.parse::<f64>().map_err(|e| e.into()))
.collect::<Result<Vec<_>>>()?;
let read_vec3 = |start: usize| {
if parts.len() < 3 {
bail!("Vec3 requires 3 components.");
}
Ok(Vector3::new(
parts[start],
parts[start + 1],
parts[start + 2],
))
};
let read_color = || {
if parts.len() < 3 {
bail!("Color requires 3 components.");
}
Ok(Color::from_01_float(parts[0], parts[1], parts[2]))
};
match keyword {
"eye" => scene.eye_pos = read_vec3(0)?,
"viewdir" => scene.view_dir = read_vec3(0)?,
"updir" => scene.up_dir = read_vec3(0)?,
"hfov" => scene.hfov = parts[0],
"bkgcolor" => scene.bkg_color = read_color()?,
"mtlcolor" => {
let idx = scene.material_colors.len();
material_color = Some(idx);
scene.material_colors.push(read_color()?);
}
"sphere" => scene.objects.push(Object {
kind: Box::new(Sphere {
center: read_vec3(0)?,
radius: parts[3],
}),
material: match material_color {
Some(v) => v,
None => bail!("Each sphere must be preceded by a `mtlcolor` line"),
},
}),
"cylinder" => scene.objects.push(Object {
kind: Box::new(Cylinder {
center: read_vec3(0)?,
direction: read_vec3(3)?,
radius: parts[6],
length: parts[7],
}),
material: match material_color {
Some(v) => v,
None => bail!("Each sphere must be preceded by a `mtlcolor` line"),
},
}),
_ => bail!("Unknown keyword {keyword}"),
}
}
}
Ok(scene)
}

151
assignment-1a/src/main.rs Normal file
View file

@ -0,0 +1,151 @@
#[macro_use]
extern crate anyhow;
mod image;
mod input_file;
mod utils;
mod ray;
mod scene;
use std::fs::File;
use std::path::PathBuf;
use anyhow::Result;
use clap::Parser;
use ordered_float::NotNan;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
use crate::image::Image;
use crate::input_file::parse_input_file;
use crate::ray::Ray;
/// Simple raycaster.
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Opt {
/// Path to the input file to use.
#[clap()]
input_path: PathBuf,
/// Path to the output (defaults to the same file name as the input except
/// with an extension of .ppm)
#[clap(short = 'o', long = "output")]
output_path: Option<PathBuf>,
/// Force parallel projection to be used
#[clap(long = "parallel")]
force_parallel: bool,
/// Override distance from eye
#[clap(long = "distance", default_value = "1.0")]
distance: f64,
}
fn main() -> Result<()> {
let opt = Opt::parse();
let out_file = opt
.output_path
.unwrap_or_else(|| opt.input_path.with_extension("ppm"));
let mut scene = parse_input_file(&opt.input_path)?;
let distance = opt.distance;
if opt.force_parallel {
scene.parallel_projection = true;
}
// Compute the viewing window
let view_window = scene.compute_viewing_window(distance);
// Translate image pixels to real-world 3d coords
let translate_pixel = {
let dx = view_window.upper_right - view_window.upper_left;
let pixel_base_x = dx / scene.image_width as f64;
let dy = view_window.lower_left - view_window.upper_left;
let pixel_base_y = dy / scene.image_height as f64;
move |px: usize, py: usize| {
let x_component = pixel_base_x * px as f64;
let y_component = pixel_base_y * py as f64;
// Without adding this, we would be getting the top-left of the pixel's
// rectangle. We want the center, so add half of the pixel size as
// well.
let center_offset = (pixel_base_x + pixel_base_y) / 2.0;
view_window.upper_left + x_component + y_component + center_offset
}
};
// Generate a parallel iterator for pixels
// The iterator preserves order and uses row-major order
let pixels_iter = (0..scene.image_height)
.into_par_iter()
.flat_map(|y| (0..scene.image_width).into_par_iter().map(move |x| (x, y)));
// Loop through every single pixel of the output file
let pixels = pixels_iter
.map(|(px, py)| {
let pixel_in_space = translate_pixel(px, py);
let ray_start = if scene.parallel_projection {
// For a parallel projection, we'll just take the view direction and
// subtract it from the target point. This means every single
// ray will be viewed from a point at infinity, rather than a single eye
// position.
let n = scene.view_dir.normalize();
let view_dir = n * distance;
pixel_in_space - view_dir
} else {
scene.eye_pos
};
let ray = Ray::from_endpoints(ray_start, pixel_in_space);
let earliest_intersection = scene
.objects
.iter()
.filter_map(|object| {
let intersection_point_opt = object.kind.intersects_ray_at(&ray);
intersection_point_opt.and_then(|t| {
// Unfortunately, IEEE floats in Rust don't have total ordering,
// because NaNs violate ordering properties. The way to remedy this
// is to ensure we don't have NaNs by wrapping it into this type,
// which then implements total ordering
let t = NotNan::new(t);
// Return both the t and the sphere, because we want to sort on the
// t but later retrieve attributes from the sphere
t.ok().map(|t| (t, object))
})
})
// Sort the list of intersection times by the lowest one.
.min_by_key(|(t, _)| *t);
match earliest_intersection {
// Take the object's material color
Some((_, object)) => scene.compute_pixel_color(object.material),
// There was no intersection, so this should default to the scene's
// background color
None => scene.bkg_color,
}
})
.collect::<Vec<_>>();
// Construct and emit image
let image = Image {
width: scene.image_width,
height: scene.image_height,
data: pixels,
};
{
let file = File::create(out_file)?;
image.write(file)?;
}
Ok(())
}

27
assignment-1a/src/ray.rs Normal file
View file

@ -0,0 +1,27 @@
use nalgebra::Vector3;
/// A normalized parametric Ray of the form (origin + direction * time)
///
/// That means at any time t: f64, the point represented by origin + direction *
/// time occurs on the ray.
#[derive(Debug)]
pub struct Ray {
pub origin: Vector3<f64>,
pub direction: Vector3<f64>,
}
impl Ray {
/// Construct a ray from endpoints
pub fn from_endpoints(start: Vector3<f64>, end: Vector3<f64>) -> Self {
let delta = (end - start).normalize();
Ray {
origin: start,
direction: delta,
}
}
/// Evaluate the ray at a certain point in time, yielding a point
pub fn eval(&self, time: f64) -> Vector3<f64> {
self.origin + self.direction * time
}
}

View file

@ -0,0 +1,126 @@
use nalgebra::Vector3;
use crate::ray::Ray;
use crate::utils::{compute_rotation_matrix, min_f64};
use super::data::ObjectKind;
#[derive(Debug)]
pub struct Cylinder {
pub center: Vector3<f64>,
pub direction: Vector3<f64>,
pub radius: f64,
pub length: f64,
}
impl ObjectKind for Cylinder {
/// Given a cylinder, returns the first time at which this ray intersects the
/// cylinder.
///
/// If there is no intersection point, returns None.
fn intersects_ray_at(&self, ray: &Ray) -> Option<f64> {
// Determine rotation matrix for turning the cylinder upright along the
// Z-axis
let target_direction = Vector3::new(0.0, 0.0, 1.0);
let rotation_matrix =
compute_rotation_matrix(self.direction, target_direction);
// Transform all parameters according to this rotation matrix
let rotated_cylinder_center = rotation_matrix * self.center;
let rotated_ray_origin = rotation_matrix * ray.origin;
let rotated_ray_direction = rotation_matrix * ray.direction;
// Now that we know the cylinder is upright, we can start checking against
// the formula:
//
// (ox + t*rx - cx)^2 + (oy + t*ry - cy)^2 = r^2
//
// where o{xy} is the ray origin, r{xy} is the ray direction, and c{xy} is
// the cylinder center. The z will be taken care of after the fact. To
// solve, we must put it into the form At^2 + Bt + c = 0. The variables
// are:
//
// A: rx^2 + ry^2
// B: 2(rx(ox - cx) + ry(oy - cy))
// C: (cx - ox)^2 + (cy - oy)^2 - r^2
let (a, b, c) = {
let o = rotated_ray_origin;
let r = rotated_ray_direction;
let c = rotated_cylinder_center;
(
r.x.powi(2) + r.y.powi(2),
2.0 * (r.x * (o.x - c.x) + r.y * (o.y - c.y)),
(c.x - o.x).powi(2) + (c.y - o.y).powi(2) - self.radius.powi(2),
)
};
let discriminant = b * b - 4.0 * a * c;
let possible_side_solutions = match discriminant {
// Discriminant < 0, means the equation has no solutions.
d if d < 0.0 => vec![],
// Discriminant == 0
d if d == 0.0 => vec![-b / 2.0 * a],
// Discriminant > 0, 2 solutions available.
d if d > 0.0 => {
vec![
(-b + discriminant.sqrt()) / (2.0 * a),
(-b - discriminant.sqrt()) / (2.0 * a),
]
}
// Probably hit some NaN or Infinity value due to faulty inputs...
_ => unreachable!("Invalid determinant value: {discriminant}"),
};
// Filter out solutions that don't have a valid Z position.
let side_solutions = possible_side_solutions.into_iter().filter(|t| {
let ray_point = ray.eval(*t);
let rotated_ray_point = rotation_matrix * ray_point;
let z = rotated_ray_point.z - rotated_cylinder_center.z;
// Check to see if z is between -len/2 and len/2
z.abs() < self.length / 2.0
});
// We also need to add solutions for the two ends of the cylinder, which
// uses a similar method except backwards: check intersection points
// with the correct z-plane and then see if the points are within the
// circle.
//
// Luckily, this means we only need to care about one dimension at first,
// and don't need to perform the quadratic equation method above.
//
// oz + t * rz = cz +- (len / 2)
// t = (oz + cz +- (len / 2)) / rz
let possible_z_intersections = {
let o = rotated_ray_origin;
let r = rotated_ray_direction;
let c = rotated_cylinder_center;
vec![
(o.z + c.z + self.length / 2.0) / r.z,
(o.z + c.z - self.length / 2.0) / r.z,
]
};
// Filter out all the solutions where the z does not lie in the circle
let end_solutions = possible_z_intersections.into_iter().filter(|t| {
let ray_point = ray.eval(*t);
ray_point.x.powi(2) + ray_point.y.powi(2) <= self.radius.powi(2)
});
let solutions = side_solutions
.into_iter()
.chain(end_solutions.into_iter())
// Remove any t < 0, since that means it's behind the viewer and we
// can't see it.
.filter(|t| *t >= 0.0);
// Return the minimum solution
min_f64(solutions)
}
}

View file

@ -0,0 +1,101 @@
use std::fmt::Debug;
use nalgebra::Vector3;
use crate::image::Color;
use crate::ray::Ray;
pub trait ObjectKind: Debug + Send + Sync {
/// Determine where the ray intersects this object, returning the earliest
/// time this happens. Returns None if no intersection occurs.
///
/// Also known as Trace_Ray in the slides, except not the part where it calls
/// Shade_Ray.
fn intersects_ray_at(&self, ray: &Ray) -> Option<f64>;
}
#[derive(Debug)]
pub struct Object {
pub kind: Box<dyn ObjectKind>,
/// Index into the scene's material color list
pub material: usize,
}
#[derive(Debug)]
pub struct Rect {
pub upper_left: Vector3<f64>,
pub upper_right: Vector3<f64>,
pub lower_left: Vector3<f64>,
pub lower_right: Vector3<f64>,
}
#[derive(Debug, Default)]
pub struct Scene {
pub eye_pos: Vector3<f64>,
pub view_dir: Vector3<f64>,
pub up_dir: Vector3<f64>,
/// Horizontal field of view (in degrees)
pub hfov: f64,
pub parallel_projection: bool,
pub image_width: usize,
pub image_height: usize,
/// Background color
pub bkg_color: Color,
pub material_colors: Vec<Color>,
pub objects: Vec<Object>,
}
impl Scene {
/// Determine the color that should be used to fill this pixel
///
/// Also known as Shade_Ray in the slides.
pub fn compute_pixel_color(&self, material_idx: usize) -> Color {
// TODO: Does it make sense to make this function fallible from an API
// design standpoint?
self
.material_colors
.get(material_idx)
.cloned()
.unwrap_or(self.bkg_color)
}
/// Determine the boundaries of the viewing window in world coordinates
pub fn compute_viewing_window(&self, distance: f64) -> Rect {
// Compute viewing directions
let u = self.view_dir.cross(&self.up_dir).normalize();
let v = u.cross(&self.view_dir).normalize();
// Compute dimensions of viewing window based on field of view
let viewing_width = {
// Divide the angle in 2 since we are trying to use trig rules so we must
// get it from a right triangle
let half_hfov = self.hfov.to_radians() / 2.0;
// tan(hfov / 2) = w / 2d
let w_over_2d = half_hfov.tan();
// To find the viewing width we must multiply by 2d now
w_over_2d * 2.0 * distance
};
let aspect_ratio = self.image_width as f64 / self.image_height as f64;
let viewing_height = viewing_width / aspect_ratio;
// Compute viewing window corners
let n = self.view_dir.normalize();
#[rustfmt::skip] // Otherwise this line wraps over
let view_window = Rect {
upper_left: self.eye_pos + n * distance - u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
upper_right: self.eye_pos + n * distance + u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
lower_left: self.eye_pos + n * distance - u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
lower_right: self.eye_pos + n * distance + u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
};
view_window
}
}

View file

@ -0,0 +1,3 @@
pub mod data;
pub mod sphere;
pub mod cylinder;

View file

@ -0,0 +1,99 @@
use nalgebra::Vector3;
use crate::{ray::Ray, utils::min_f64};
use super::data::ObjectKind;
#[derive(Debug)]
pub struct Sphere {
pub center: Vector3<f64>,
pub radius: f64,
}
impl ObjectKind for Sphere {
/// Given a sphere, returns the first time at which this ray intersects the
/// sphere.
///
/// If there is no intersection point, returns None.
fn intersects_ray_at(&self, ray: &Ray) -> Option<f64> {
let a = ray.direction.x.powi(2)
+ ray.direction.y.powi(2)
+ ray.direction.z.powi(2);
let b = 2.0
* (ray.direction.x * (ray.origin.x - self.center.x)
+ ray.direction.y * (ray.origin.y - self.center.y)
+ ray.direction.z * (ray.origin.z - self.center.z));
let c = (ray.origin.x - self.center.x).powi(2)
+ (ray.origin.y - self.center.y).powi(2)
+ (ray.origin.z - self.center.z).powi(2)
- self.radius.powi(2);
let discriminant = b * b - 4.0 * a * c;
match discriminant {
// Discriminant < 0, means the equation has no solutions.
d if d < 0.0 => None,
// Discriminant == 0
d if d == 0.0 => Some(-b / (2.0 * a)),
d if d > 0.0 => {
let solution_1 = (-b + discriminant.sqrt()) / (2.0 * a);
let solution_2 = (-b - discriminant.sqrt()) / (2.0 * a);
let solutions = [solution_1, solution_2]
.into_iter()
// Remove any t < 0, since that means it's behind the viewer and we
// can't see it.
.filter(|t| *t >= 0.0);
// Return the minimum solution
min_f64(solutions)
}
// Probably hit some NaN or Infinity value due to faulty inputs...
_ => unreachable!("Invalid determinant value: {discriminant}"),
}
}
}
#[cfg(test)]
mod tests {
use nalgebra::Vector3;
use crate::ray::Ray;
use crate::scene::data::ObjectKind;
use super::Sphere;
#[test]
fn practice_problem_slide_154() {
let ray = Ray {
origin: Vector3::new(0.0, 0.0, 0.0),
direction: Vector3::new(0.0, 0.0, -1.0),
};
let sphere = Sphere {
center: Vector3::new(0.0, 0.0, -10.0),
radius: 4.0,
};
let point = sphere.intersects_ray_at(&ray).map(|t| ray.eval(t));
// the intersection point in this case is (0, 0, -6)
assert_eq!(point, Some(Vector3::new(0.0, 0.0, -6.0)));
}
#[test]
fn practice_problem_slide_158() {
let ray = Ray {
origin: Vector3::new(0.0, 0.0, 0.0),
direction: Vector3::new(0.0, 0.5, -1.0),
};
let sphere = Sphere {
center: Vector3::new(0.0, 0.0, -10.0),
radius: 4.0,
};
// oops! In this case, the ray does not intersect the sphere.
assert_eq!(sphere.intersects_ray_at(&ray), None);
}
}

View file

@ -0,0 +1,32 @@
use nalgebra::{Matrix3, Vector3};
use ordered_float::NotNan;
/// Finds the minimum of an iterator of f64s, ignoring any NaN values
pub fn min_f64<I>(i: I) -> Option<f64>
where
I: Iterator<Item = f64>,
{
i.filter_map(|i| NotNan::new(i).ok())
.min()
.map(|i| i.into_inner())
}
/// Calculate the rotation matrix between the 2 given vectors
/// Based on the method here: https://math.stackexchange.com/a/897677
pub fn compute_rotation_matrix(
a: Vector3<f64>,
b: Vector3<f64>,
) -> Matrix3<f64> {
let cos_t = a.dot(&b);
let sin_t = a.cross(&b).norm();
let g = Matrix3::new(cos_t, -sin_t, 0.0, sin_t, cos_t, 0.0, 0.0, 0.0, 1.0);
// New basis vectors
let u = a;
let v = (b - a.dot(&b) * a).normalize();
let w = b.cross(&a);
// Not sure if this is required to be invertible?
let f = Matrix3::from_columns(&[u, v, w]).try_inverse().unwrap();
f.try_inverse().unwrap() * g * f
}

132
assignment-1a/writeup.md Normal file
View file

@ -0,0 +1,132 @@
---
geometry: margin=2cm
output: pdf_document
---
# Raycaster
#### Michael Zhang \<zhan4854@umn.edu\>
---
Determining the viewing window for the raycaster for this assignment involved
creating a "virtual" screen in world coordinates, mapping image pixels into that
virtual screen, and then casting a ray through each pixel's world coordinate to
see where it would intersect objects.
### Creating a virtual screen
The virtual screen is determined first using the eye's position and where it's
looking. This gives us a single 3d vector, but it doesn't give us a 2d screen in
the world. This is where the field of view (FOV) comes in; the FOV determines
how many degrees the screen should take up.
![Field of view](doc/fov.jpg){width=180px}
Changing the angle of the field of view would result in a wider or narrower
screen, which when paired with the aspect ratio (width / height), would produce
a bigger or smaller viewing screen, like the orange box in the above diagram
shows. Simply put, FOV affects how _much_ of the frame you're able to see. An
example is shown here:
![](examples/fov-demo-1.png){width=180px}\ ![](examples/fov-demo-2.png){width=180px}
The left image uses an FOV of 60, while the right image uses an FOV of 30. As
you can see, the left side has a wider range of vision, which allows it to see
more of the world. (both images can be found in the `examples` directory of the
handin zip)
Curiously, distance from the eye actually doesn't really affect the viewing
screen very much. The reason is the screen is only used to determine how to
project rays. As the two black rectangles in the diagram above demonstrates,
changing the distance would still allow the viewer to see the same amount of the
scene. (using the word _amount_ very loosely here to mean percentage of the
landscape, rather than # of pixels, which is determined by the actual image
dimensions)
The up-direction vector controls the rotation of the scene. Without the
up-direction, it would not be possible to tell which rotation the screen should
be in.
![Rotation determined by up direction](doc/rot.jpg){width=240px}
To see what this looks like, consider the following images, where the left side
uses an up direction of $(0, 1, 0)$, while the right side uses $(1, 1, 0)$ (both
images can be found in the `examples` directory of the handin zip)
![](examples/up-dir-demo-1.png){width=180px}\ ![](examples/up-dir-demo-2.png){width=180px}
Together, all of these parameters can uniquely determine a virtual screen
location, that we can use to cast rays through and fill pixels. We can change
any of these to produce an image with a more exaggerated view of the scene for
example; simply move the eye position to be incredibly close to the object that
we are observing, and increase the field of view to cover the entire object.
Because the rays are going in much different directions and travelling different
distances, the corners of the image will seem more stretched than if we were
observing the object from afar and all the rays are in approximately the same
part of the virtual screen.
One other point to make is that we're currently using a rectangle for our
virtual screen, which automatically does a bit of the distortion. If instead we
were to use a curved lens-like shape, then the rays pointing to any pixel of the
screen would be travelling the same distance. Moving the eye position closer to
the object would still generate distortion, but to a lesser extent.
### Mapping image pixels
After the rectangle has been determined, we can simply pick one corner to start
as an anchor, and then find out what pixel values would correspond to it. For
example, in the image below:
![Mapping image pixels](doc/map.jpg){width=240px}
I would pick a starting point like $A$, and then take the vector $B-A$ and
subdivide it into 4 pieces, letting $\Delta x = \frac{B-A}{4}$. Then, same thing
for the $y$ direction, I would set $\Delta y = \frac{D-A}{4}$. Taking $A +
x_i \times \Delta x + y_i \times \Delta y$ yields the precise coordinate
location for any pixel.
(Technically really we would want the middle of the pixel, so just add
$\frac{\Delta x + \Delta y}{2}$ to the point to get that)
### Parallel Projection Notes
Because of the way I implemented parallel projection, it's recommended to
either put the eye much farther back, or use `--distance` to force a much bigger
distance from the eye for the raycaster. See the `--help` to see how this option
is used.
### Cylinder Intersection Notes
First, we will transform the current point into the vector space of the
cylinder, so that the cylinder location is $(0, 0, 0)$ and the direction vector
is normalized into $(0, 0, 1)$.
This can be done by using a rotation matrix (since we are sure this
transformation is just a rotation). This rotation is actually a 2D rotation,
around the normal between the cylinder direction and $(0, 0, 1)$. We then
rotate everything we are working with (the cylinder and the ray) into this
coordinate system to make calculations easier.
Then it's a matter of determining if the $x$ and $y$ coordinates fall into the
space constrained by the equation $(o_x + t\times r_x - c_x)^2 + (o_y + t\times
r_y - c_y)^2 = r^2$ and if $z \le L$. I can solve this using the quadratic
formula the same way as the sphere case.
We want a quadratic equation of the form $At^2 + Bt + C = 0$. The values for
$A$, $B$, and $C$ are:
- $A = r_x^2 + r_y^2$
- $B = 2(r_x(o_x - c_x) + r_y(o_y - c_y))$
- $C = (c_x - o_x)^2 + (c_y - o_y)^2 - r^2$
Solving this for $t$ yields 0-2 solutions depending on if the equation was
satisfied or not. Then, we can plug any solutions we get back into the ray
equation and determine if the $z$-coordinate is in the range of the cylinder
that we want.
We will also have to do this for the ends of the cylinder, but just backwards.
So we would start with the $z$-coordinate, solve for $t$s where the ray hits
that $z$-plane, and then check the $x$ and $y$ values to see if they satisfy the
ray equation as well.

10
assignment-1b/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
/target
/assignment-1b
/raytracer1b
/examples/*.png
*.ppm
*.zip
*.pdf
perf.data*
flamegraph.svg
showcase.png

View file

838
assignment-1b/Cargo.lock generated Normal file
View file

@ -0,0 +1,838 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "assignment-1b"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"clap",
"derivative",
"nalgebra",
"num",
"ordered-float",
"rand",
"rayon",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "io-lifetimes"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "matrixmultiply"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
dependencies = [
"rawpointer",
]
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "nalgebra"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6515c882ebfddccaa73ead7320ca28036c4bc84c9bcca3cc0cbba8efe89223a"
dependencies = [
"approx",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d232c68884c0c99810a5a4d333ef7e47689cfd0edc85efc9e54e1e6bf5212766"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-complex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "ordered-float"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf"
dependencies = [
"num-traits",
]
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "paste"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "rustix"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "safe_arch"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529"
dependencies = [
"bytemuck",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "simba"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50582927ed6f77e4ac020c057f37a268fc6aebc29225050365aacbb9deeeddc4"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
"wide",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wide"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae41ecad2489a1655c8ef8489444b0b113c0a0c795944a3572a0931cf7d2525c"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc",
"windows_i686_gnu",
"windows_i686_msvc",
"windows_x86_64_gnu",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

22
assignment-1b/Cargo.toml Normal file
View file

@ -0,0 +1,22 @@
[package]
name = "assignment-1b"
authors = ["Michael Zhang <zhan4854@umn.edu>"]
version = "0.1.0"
edition = "2021"
[[bin]]
name = "raytracer1b"
path = "src/main.rs"
[dependencies]
anyhow = "1.0.68"
base64 = "0.21.0"
clap = { version = "4.1.4", features = ["cargo", "derive"] }
derivative = "2.2.0"
nalgebra = "0.32.1"
num = { version = "0.4.0", features = ["serde"] }
ordered-float = "3.4.0"
rand = "0.8.5"
rayon = "1.6.1"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"

54
assignment-1b/Makefile Normal file
View file

@ -0,0 +1,54 @@
.PHONY: all clean
.PRECIOUS: $(EXAMPLES_PPM)
RAYTRACER_FLAGS :=
DOCKER := docker
ZIP := zip
PANDOC := pandoc
CONVERT := convert
HANDIN := ./hw1b.michael.zhang.zip
BINARY := ./raytracer1b
WRITEUP := ./writeup.pdf
SHOWCASE := ./showcase.png
SOURCES := Cargo.toml $(shell find -name "*.rs")
EXAMPLES := $(shell find examples -name "*.txt")
EXAMPLES_PPM := $(patsubst %.txt,%.ppm,$(EXAMPLES))
EXAMPLES_PNG := $(patsubst %.txt,%.png,$(EXAMPLES))
all: $(HANDIN)
$(BINARY): $(SOURCES)
mkdir -p target/docker
$(DOCKER) run \
--rm \
-v "$(shell pwd)":/usr/src/myapp \
-v cargo-registry:/usr/local/cargo \
--user "$(shell id -u)":"$(shell id -g)" \
-w /usr/src/myapp \
-e CARGO_TARGET_DIR=/usr/src/myapp/target/docker \
rust \
cargo build --profile release-handin
mv target/docker/release-handin/raytracer1b $@
$(HANDIN): $(BINARY) $(WRITEUP) Makefile Cargo.toml Cargo.lock README.md $(EXAMPLES_PNG) $(EXAMPLES_PPM) $(SHOWCASE)
$(ZIP) -r $@ src examples $^
$(SHOWCASE): examples/soft-shadow-demo.png
cp $< $@
examples/%.ppm: examples/%.txt $(SOURCES)
cargo run --release -- -o $@ $(RAYTRACER_FLAGS) $<
examples/%.png: examples/%.ppm
convert $< $@
writeup.pdf: writeup.md $(EXAMPLES_PNG)
$(PANDOC) -o $@ $<
clean:
rm -rf target/docker \
$(HANDIN) $(BINARY) $(WRITEUP) $(SHOWCASE) \
$(EXAMPLES_PPM) $(EXAMPLES_PNG)

29
assignment-1b/README.md Normal file
View file

@ -0,0 +1,29 @@
# Raycaster
## Bundle contents
Writeup is located at `/writeup.pdf`.
The binary can be found at `/raytracer1b`. Run `./raytracer1b --help` to see
how to use it. The binary has been built using the Rust Docker image, which
should have an environment similar to CSELabs. If there is trouble running the
binary, try building from source, as documented below.
Examples are found in the `examples` directory. The text files are the input
sources, and the ppm files are the corresponding outputs. They have been
generated by running this program. For convenience, pngs have also been provided
using imagemagick.
## Showcase image
The showcase image can be found at `/showcase.png`.
## Building from source
The Makefile currently uses Docker to produce a more consistent build. If you
have a Rust+Cargo toolchain installed locally, it's also possible to build the
source using just:
cargo build --release
The binary will be found in `target/release`.

View file

@ -0,0 +1,18 @@
imsize 512 512
eye 0 0 0
viewdir 0 0.1 -1
hfov 90
updir 0 1 0
bkgcolor 0.1 0.1 0.1
light -1 -1 -1 0 0.9 0.5 0.05
mtlcolor 0 1 0 1 1 1 0.6 0.2 0.2 10
sphere 0 1.5 -4 1
mtlcolor 0 1 0 1 1 1 0.1 0.8 0.2 10
sphere -1.275 -0.75 -4 1
mtlcolor 0 1 0 1 1 1 0.1 0.2 0.8 10
sphere 1.275 -0.75 -4 1

View file

@ -0,0 +1,10 @@
eye 0 0 0
viewdir 0 -0.3 -1
updir 0 1 0
hfov 60
imsize 256 512
bkgcolor 0.1 0.1 0.1
light 0 -1 0 0 1 1 1
mtlcolor 1 1 0 1 1 1 0.2 0.6 0.2 20
sphere 0 -2 -3 1
sphere 0 0 -3 0.5

View file

@ -0,0 +1,15 @@
imsize 600 200
eye 0 0 15
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.4 0.4 0.4
attlight -15 10 5 1 1 1 1 0 0.25 0.03
mtlcolor 0.6 1 0.8 1 1 1 0.4 1 0.5 15
sphere -10 0 0 2
sphere -5 0 0 2
sphere 0 0 0 2
sphere 5 0 0 2
sphere 10 0 0 2

View file

@ -0,0 +1,12 @@
imsize 640 480
eye 0 0 15
viewdir 0 0 -1
hfov 60
updir 0 1 0
bkgcolor 0.1 0.1 0.1
light -10 10 -3 0 0 0.4 0.4
light 10 10 -3 1 0.4 0 0.4
mtlcolor 0.5 1 0.5 0.2 0.4 0.8 0.2 0.4 0 10
cylinder -2 4 -3 0 0 5 1 4

View file

@ -0,0 +1,15 @@
imsize 640 480
eye -2.5 2 -5
viewdir -0.2 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.4 0.4 0.4
depthcueing 0.4 0.4 0.4 1 0.1 60 0
light 0 5 -7.5 1 1 1 1
mtlcolor 1 0.8 0.7 1 1 1 0.6 0.6 0 10
sphere -5 2 -10 2
sphere -3 2 -20 2
sphere 1.5 2 -30 2
sphere 13 2 -60 2

View file

@ -0,0 +1,23 @@
imsize 600 200
eye 0 0 15
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.4 0.4 0.4
light 0 5 5 1 0.7 0.7 0.8
mtlcolor 0.6 1 0.4 1 1 1 0 0.4 0 1
sphere -10 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.4 0 1
sphere -5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.4 0.4 0 1
sphere 0 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.6 0.4 0 1
sphere 5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.8 0.4 0 1
sphere 10 0 0 2

View file

@ -0,0 +1,23 @@
imsize 600 200
eye 0 0 15
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.4 0.4 0.4
light 0 5 5 1 0.7 0.7 0.8
mtlcolor 0.6 1 0.4 1 1 1 0.2 0 0 1
sphere -10 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0 1
sphere -5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.4 0 1
sphere 0 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.6 0 1
sphere 5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.8 0 1
sphere 10 0 0 2

View file

@ -0,0 +1,23 @@
imsize 600 200
eye 0 0 15
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.4 0.4 0.4
light 0 5 5 1 0.7 0.7 0.8
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0 15
sphere -10 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.2 15
sphere -5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.4 15
sphere 0 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.6 15
sphere 5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.8 15
sphere 10 0 0 2

View file

@ -0,0 +1,16 @@
imsize 640 480
eye 0 0 -2
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.4 0.4 0.4
light -5 5 -7.5 1 1 1 1
light 5 5 -7.5 1 1 1 1
mtlcolor 0.4 0.6 1 1 1 1 0.2 0.6 0 10
sphere 0 -2 -10 2
mtlcolor 0.8 0.4 0.4 1 1 1 0.2 0.6 0 10
sphere -2.5 2.5 -10 1
sphere 2.5 2.5 -10 1

View file

@ -0,0 +1,23 @@
imsize 600 200
eye 0 0 15
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.4 0.4 0.4
light 0 5 5 1 0.7 0.7 0.8
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.4 2
sphere -10 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.4 6
sphere -5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.4 10
sphere 0 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.4 50
sphere 5 0 0 2
mtlcolor 0.6 1 0.4 1 1 1 0.2 0.2 0.4 100
sphere 10 0 0 2

View file

@ -0,0 +1,37 @@
imsize 1920 1080
eye 0 0 15
viewdir 0 0 -1
hfov 60
updir 0 1 0
bkgcolor 0.4 0.4 0.4
depthcueing 0.4 0.4 0.4 1 0.1 60 0
light -10 10 -3 0 0.8 0.8 0.8
light -10 10 -3 1 0.8 0.8 0.8
mtlcolor 0 0.5 0.5 1 1 1 0.2 0.7 0.2 10
sphere -1 -2 -5 2
sphere 10 -10 -40 1
mtlcolor 0.5 0.5 1 0.4 0.4 0.4 0.2 0.7 0.4 10
sphere 1 2 -3 3
sphere -6 3 -4 1
mtlcolor 1.0 0 0.5 0.6 0.4 0.2 0.2 0.6 0.8 10
sphere 20 20 -50 6
mtlcolor 0.5 0 0.5 0.6 0.4 0.2 0.2 0.5 0 10
sphere -6 -4 -8 7
mtlcolor 0.8 0 0.3 0.6 0.4 0.2 0.2 0.4 0.2 10
cylinder -2 2 -3 0 0 5 1 4
mtlcolor 0.8 0.8 0.3 0.6 0.4 0.2 0.2 0.8 0.2 10
sphere -40 35 -80 7
sphere -25 20 -60 5
sphere -12.5 15 -40 3
sphere -7 14 -30 2
mtlcolor 0.5 1 0.5 0.2 0.4 0.8 0.2 0.4 0 10
cylinder 5 1 -2 1 -2 1 1 2

View file

@ -0,0 +1,12 @@
imsize 256 256
eye 0 0 0
viewdir 0 0 -1
hfov 90
updir 0 1 0
bkgcolor 0.1 0.1 0.1
light -2 3 -3 0 1 0.5 0.6
light 2 3 -3 1 0.5 0.5 1
mtlcolor 0 1 0 1 1 1 0.2 0.4 0 10
sphere 0 0 -5 2

View file

@ -0,0 +1,45 @@
imsize 640 480
eye 0 0 15
viewdir 0 0 -1
hfov 60
updir 0 1 0
bkgcolor 0.5 0.5 0.5
depthcueing 0.5 0.5 0.5 1 0.4 60 0
light 10 10 -10 1 1 1 1
mtlcolor 0.5 1 0.5 1 1 1 0.2 1 0.1 5
sphere 4.5 4.5 -20 4.5
sphere -4.5 -4.5 -20 4.5
mtlcolor 1 0.5 0.5 1 1 1 0.2 0.8 0 5
sphere -10 0 -30 4
sphere -20 0 -30 4
sphere -30 0 -30 4
sphere -40 0 -30 4
sphere 0 0 -30 4
sphere 10 0 -30 4
sphere 20 0 -30 4
sphere 30 0 -30 4
sphere 40 0 -30 4
sphere -10 -10 -30 4
sphere -20 -10 -30 4
sphere -30 -10 -30 4
sphere -40 -10 -30 4
sphere 0 -10 -30 4
sphere 10 -10 -30 4
sphere 20 -10 -30 4
sphere 30 -10 -30 4
sphere 40 -10 -30 4
sphere -10 10 -30 4
sphere -20 10 -30 4
sphere -30 10 -30 4
sphere -40 10 -30 4
sphere 0 10 -30 4
sphere 10 10 -30 4
sphere 20 10 -30 4
sphere 30 10 -30 4
sphere 40 10 -30 4

View file

@ -0,0 +1,41 @@
use std::io::{Result, Write};
use nalgebra::Vector3;
/// A pixel color represented by a red, green, and blue value in the range 0-1.
pub type Color = Vector3<f64>;
/// A representation of an image
pub struct Image {
/// Width in pixels
pub width: usize,
/// Height in pixels
pub height: usize,
/// Pixel data in row-major form.
pub data: Vec<Color>,
}
impl Image {
/// Write the image in PPM format to a file.
pub fn write(&self, mut w: impl Write) -> Result<()> {
// Header
let header = format!("P3 {} {} 255\n", self.width, self.height);
w.write_all(header.as_bytes())?;
// Pixel data
assert_eq!(self.data.len(), self.width * self.height);
for pixel in self.data.iter() {
let pixel = pixel * 256.0;
let red = pixel.x as u8;
let green = pixel.y as u8;
let blue = pixel.z as u8;
let pixel = format!("{red} {green} {blue}\n");
w.write_all(pixel.as_bytes())?;
}
Ok(())
}
}

13
assignment-1b/src/lib.rs Normal file
View file

@ -0,0 +1,13 @@
#![doc = include_str!("../README.md")]
#[macro_use]
extern crate anyhow;
#[macro_use]
extern crate derivative;
#[macro_use]
extern crate tracing;
pub mod image;
pub mod ray;
pub mod scene;
pub mod utils;

138
assignment-1b/src/main.rs Normal file
View file

@ -0,0 +1,138 @@
#[macro_use]
extern crate tracing;
use std::fs::File;
use std::path::PathBuf;
use anyhow::Result;
use assignment_1b::image::Image;
use assignment_1b::ray::Ray;
use assignment_1b::scene::Scene;
use clap::Parser;
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
/// Simple raytracer with Blinn-Phong illumination and shadowing.
#[derive(Parser)]
#[clap(author, version, about, long_about = None)]
struct Opt {
/// Path to the input file to use.
#[clap()]
input_path: PathBuf,
/// Path to the output (defaults to the same file name as the input except
/// with an extension of .ppm)
#[clap(short = 'o', long = "output")]
output_path: Option<PathBuf>,
/// Force parallel projection to be used
#[clap(long = "parallel")]
force_parallel: bool,
/// Override distance from eye
#[clap(long = "distance", default_value = "1.0")]
distance: f64,
}
fn main() -> Result<()> {
let opt = Opt::parse();
// Set up logging
tracing_subscriber::fmt()
.with_target(false)
.with_timer(tracing_subscriber::fmt::time::uptime())
.with_level(true)
.init();
// Rename the output file if it's not provided
let out_file = opt
.output_path
.unwrap_or_else(|| opt.input_path.with_extension("ppm"));
let mut scene = Scene::from_input_file(&opt.input_path)?;
let distance = opt.distance;
// Force-override parallel projection
if opt.force_parallel {
scene.parallel_projection = true;
}
// Translate image pixels to real-world 3d coords
let translate_pixel = scene.pixel_translation_function(distance);
// Generate a parallel iterator for pixels
// The iterator preserves order and uses row-major order
let pixels_iter = (0..scene.image_height)
.into_par_iter()
.flat_map(|y| (0..scene.image_width).into_par_iter().map(move |x| (x, y)));
// Loop through every single pixel of the output file
let pixels = pixels_iter
.map(|(px, py)| {
let pixel_in_space = translate_pixel(px, py);
let ray_start = if scene.parallel_projection {
// For a parallel projection, we'll just take the view direction and
// subtract it from the target point. This means every single
// ray will be viewed from a point at infinity, rather than a single eye
// position.
let n = scene.view_dir.normalize();
let view_dir = n * distance;
pixel_in_space - view_dir
} else {
scene.eye_pos
};
let ray = Ray::from_endpoints(ray_start, pixel_in_space);
let intersections = scene
.objects
.iter()
.enumerate()
.filter_map(|(i, object)| {
match object.kind.intersects_ray_at(&ray) {
Ok(Some(t)) => {
// Return both the t and the sphere, because we want to sort on
// the t but later retrieve attributes from the sphere
Some(Ok((i, t, object)))
}
Ok(None) => None,
Err(err) => {
error!("Error: {err}");
Some(Err(err))
}
}
})
.collect::<Result<Vec<_>>>()?;
// Sort the list of intersection times by the lowest one.
let earliest_intersection =
intersections.into_iter().min_by_key(|(_, t, _)| t.time);
Ok(match earliest_intersection {
// Take the object's material color
Some((obj_idx, intersection_context, object)) => scene
.compute_pixel_color(obj_idx, object.material, intersection_context),
// There was no intersection, so this should default to the scene's
// background color
None => scene.bkg_color,
})
})
.collect::<Result<Vec<_>>>()?;
// Construct and emit image
let image = Image {
width: scene.image_width,
height: scene.image_height,
data: pixels,
};
{
let file = File::create(out_file)?;
image.write(file)?;
}
Ok(())
}

27
assignment-1b/src/ray.rs Normal file
View file

@ -0,0 +1,27 @@
use nalgebra::Vector3;
/// A normalized parametric Ray of the form (origin + direction * time)
///
/// That means at any time t: f64, the point represented by origin + direction *
/// time occurs on the ray.
#[derive(Debug)]
pub struct Ray {
pub origin: Vector3<f64>,
pub direction: Vector3<f64>,
}
impl Ray {
/// Construct a ray from endpoints
pub fn from_endpoints(start: Vector3<f64>, end: Vector3<f64>) -> Self {
let delta = (end - start).normalize();
Ray {
origin: start,
direction: delta,
}
}
/// Evaluate the ray at a certain point in time, yielding a point
pub fn eval(&self, time: f64) -> Vector3<f64> {
self.origin + self.direction * time
}
}

View file

@ -0,0 +1,204 @@
use anyhow::Result;
use nalgebra::Vector3;
use ordered_float::NotNan;
use crate::ray::Ray;
use crate::utils::compute_rotation_matrix;
use super::{illumination::IntersectionContext};
#[derive(Debug)]
pub struct Cylinder {
pub center: Vector3<f64>,
pub direction: Vector3<f64>,
pub radius: f64,
pub length: f64,
}
impl Cylinder {
/// Given a cylinder, returns the first time at which this ray intersects the
/// cylinder.
///
/// If there is no intersection point, returns None.
pub fn intersects_ray_at(
&self,
ray: &Ray,
) -> Result<Option<IntersectionContext>> {
// Determine rotation matrix for turning the cylinder upright along the
// Z-axis
let target_direction = Vector3::new(0.0, 0.0, 1.0);
let rotation_matrix =
compute_rotation_matrix(self.direction, target_direction)?;
let inverse_rotation_matrix =
rotation_matrix.try_inverse().ok_or_else(|| {
anyhow!("Rotation matrix for some reason does not have an inverse?")
})?;
// Transform all parameters according to this rotation matrix
let rotated_cylinder_center = rotation_matrix * self.center;
let rotated_ray_origin = rotation_matrix * ray.origin;
let rotated_ray_direction = rotation_matrix * ray.direction;
// Now that we know the cylinder is upright, we can start checking against
// the formula:
//
// (ox + t*rx - cx)^2 + (oy + t*ry - cy)^2 = r^2
//
// where o{xy} is the ray origin, r{xy} is the ray direction, and c{xy} is
// the cylinder center. The z will be taken care of after the fact. To
// solve, we must put it into the form At^2 + Bt + c = 0. The variables
// are:
//
// A: rx^2 + ry^2
// B: 2(rx(ox - cx) + ry(oy - cy))
// C: (cx - ox)^2 + (cy - oy)^2 - r^2
let (a, b, c) = {
let o = rotated_ray_origin;
let r = rotated_ray_direction;
let c = rotated_cylinder_center;
(
r.x.powi(2) + r.y.powi(2),
2.0 * (r.x * (o.x - c.x) + r.y * (o.y - c.y)),
(c.x - o.x).powi(2) + (c.y - o.y).powi(2) - self.radius.powi(2),
)
};
let discriminant = b * b - 4.0 * a * c;
let possible_side_solutions = match discriminant {
// Discriminant < 0, means the equation has no solutions.
d if d < 0.0 => vec![],
// Discriminant == 0
d if d == 0.0 => vec![-b / 2.0 * a],
// Discriminant > 0, 2 solutions available.
d if d > 0.0 => {
vec![
(-b + discriminant.sqrt()) / (2.0 * a),
(-b - discriminant.sqrt()) / (2.0 * a),
]
}
// Probably hit some NaN or Infinity value due to faulty inputs...
_ => bail!("Invalid determinant value: {discriminant}"),
};
// Filter out solutions that don't have a valid Z position.
let side_solutions = possible_side_solutions.into_iter().filter_map(|t| {
let ray_point = ray.eval(t);
let rotated_ray_point = rotation_matrix * ray_point;
let z = rotated_ray_point.z - rotated_cylinder_center.z;
// Check to see if z is between -len/2 and len/2
if z.abs() > self.length / 2.0 {
return None;
}
let time = NotNan::new(t).ok()?;
// The point on the center of the cylinder that corresponds to the z-axis
// point of the intersection
let center_at_z = {
let mut center_point = rotation_matrix * ray_point;
center_point.x = rotated_cylinder_center.x;
center_point.y = rotated_cylinder_center.y;
inverse_rotation_matrix * center_point
};
let normal = (ray_point - center_at_z).normalize();
Some(IntersectionContext {
time,
point: ray_point,
normal,
})
});
// We also need to add solutions for the two ends of the cylinder, which
// uses a similar method except backwards: check intersection points
// with the correct z-plane and then see if the points are within the
// circle.
//
// Luckily, this means we only need to care about one dimension at first,
// and don't need to perform the quadratic equation method above.
//
// oz + t * rz = cz +- (len / 2)
// t = (-oz + cz +- (len / 2)) / rz
let possible_z_intersections = {
let o = rotated_ray_origin;
let r = rotated_ray_direction;
let c = rotated_cylinder_center;
if r.z == 0.0 {
Vec::new() // No solutions here
} else {
vec![
(-o.z + c.z + self.length / 2.0) / r.z,
(-o.z + c.z - self.length / 2.0) / r.z,
]
}
};
let end_solutions = possible_z_intersections.into_iter().filter_map(|t| {
let ray_point = ray.eval(t);
let rotated_point = rotation_matrix * ray_point;
// Filter out all the solutions where the intersection point does not lie
// in the circle
if rotated_point.x.powi(2) + rotated_point.y.powi(2) > self.radius.powi(2)
{
return None;
}
let normal_rotated =
Vector3::new(0.0, 0.0, rotated_point.z - rotated_cylinder_center.z)
.normalize();
let normal = inverse_rotation_matrix * normal_rotated;
let time = NotNan::new(t).ok()?;
Some(IntersectionContext {
time,
point: ray_point,
normal,
})
});
let solutions = side_solutions
.into_iter()
.chain(end_solutions.into_iter())
// Remove any t < 0, since that means it's behind the viewer and we
// can't see it.
.filter(|ctx| *ctx.time >= 0.0);
// Return the minimum solution
Ok(solutions.min_by_key(|ctx| ctx.time))
}
}
#[cfg(test)]
mod tests {
use nalgebra::Vector3;
use crate::{ray::Ray};
use super::Cylinder;
#[test]
fn test_cylinder() {
let cylinder = Cylinder {
center: Vector3::new(0.0, 0.0, 0.0),
direction: Vector3::new(0.0, 1.0, 0.0),
radius: 3.0,
length: 4.0,
};
let eye = Vector3::new(0.0, 3.0, 3.0);
let end = Vector3::new(0.0, 2.0, 2.0);
let ray = Ray::from_endpoints(eye, end);
let res = cylinder.intersects_ray_at(&ray);
panic!("Result: {res:?}");
}
}

View file

@ -0,0 +1,225 @@
use std::fmt::Debug;
use anyhow::Result;
use nalgebra::Vector3;
use crate::image::Color;
use crate::ray::Ray;
use crate::utils::cross;
use super::cylinder::Cylinder;
use super::illumination::IntersectionContext;
use super::sphere::Sphere;
use super::Scene;
#[derive(Debug)]
pub enum ObjectKind {
Sphere(Sphere),
Cylinder(Cylinder),
}
impl ObjectKind {
/// Determine where the ray intersects this object, returning the earliest
/// time this happens. Returns None if no intersection occurs.
///
/// Also known as Trace_Ray in the slides, except not the part where it calls
/// Shade_Ray.
pub fn intersects_ray_at(
&self,
ray: &Ray,
) -> Result<Option<IntersectionContext>> {
match self {
ObjectKind::Sphere(sphere) => sphere.intersects_ray_at(ray),
ObjectKind::Cylinder(cylinder) => cylinder.intersects_ray_at(ray),
}
}
}
/// An object in the scene
#[derive(Debug)]
pub struct Object {
pub kind: ObjectKind,
/// Index into the scene's material color list
pub material: usize,
}
#[derive(Debug)]
pub struct Rect {
pub upper_left: Vector3<f64>,
pub upper_right: Vector3<f64>,
pub lower_left: Vector3<f64>,
pub lower_right: Vector3<f64>,
}
#[derive(Debug)]
pub struct Material {
pub diffuse_color: Vector3<f64>,
pub specular_color: Vector3<f64>,
pub k_a: f64,
pub k_d: f64,
pub k_s: f64,
pub exponent: f64,
}
#[derive(Debug)]
pub enum LightKind {
/// A point light source exists at a point and emits light in all directions
Point {
location: Vector3<f64>,
/// Whether light attenuation is enabled for this light
attenuation: Option<Attenuation>,
},
/// A directional light source exists at an infinitely far location but emits
/// light in a specific direction
Directional { direction: Vector3<f64> },
}
#[derive(Debug)]
pub struct Light {
/// The kind of light source, as well as its associated information
pub kind: LightKind,
/// The color, or intensity, of the light source
pub color: Vector3<f64>,
}
impl Light {
/// Get the unit directional vector pointing from the given point to this
/// light source
pub fn direction_from(&self, point: Vector3<f64>) -> Vector3<f64> {
match self.kind {
LightKind::Point { location, .. } => location - point,
LightKind::Directional { direction } => -direction,
}
.normalize()
}
}
#[derive(Debug)]
pub struct DepthCueing {
/// The color to tint (should be the same as the background color, to avoid
/// bizarre visual effects)
pub color: Color,
/// Proportion of the color influenced by the depth tint when the distance is
/// maxed (caps at 1.0)
pub a_max: f64,
/// Proportion of the color influenced by the depth tint when the distance is
/// at the minimum (caps at 1.0)
pub a_min: f64,
/// The max distance that should be affected by the depth tint
pub dist_max: f64,
/// The min distance that should be affected by the depth tint
pub dist_min: f64,
}
/// A default implementation here needs to simulate what would happen if there
/// was no depth cueing. In this case, if we have both a_max and a_min be 1.0,
/// then the original color will always apply and there will be no need for
/// depth color
impl Default for DepthCueing {
fn default() -> Self {
Self {
color: Default::default(),
a_max: 1.0,
a_min: 1.0,
dist_max: 0.0,
dist_min: 0.0,
}
}
}
/// Light attenuation dropoff coefficients
#[derive(Debug)]
pub struct Attenuation {
pub c1: f64,
pub c2: f64,
pub c3: f64,
}
/// A default implementation here needs to simulate what would happen if there
/// was no light attenuation specified. In this case, c1 would just be a
/// constant of 1 and all the coefficients for anything involving distance would
/// be zeroed out
impl Default for Attenuation {
fn default() -> Self {
Self {
c1: 1.0,
c2: 0.0,
c3: 0.0,
}
}
}
impl Scene {
/// Determine the boundaries of the viewing window in world coordinates
pub fn compute_viewing_window(&self, distance: f64) -> Rect {
// Compute viewing directions
let u = cross(self.view_dir, self.up_dir).normalize();
let v = cross(u, self.view_dir).normalize();
// Compute dimensions of viewing window based on field of view
let viewing_width = {
// Divide the angle in 2 since we are trying to use trig rules so we must
// get it from a right triangle
let half_hfov = self.hfov.to_radians() / 2.0;
// tan(hfov / 2) = w / 2d
let w_over_2d = half_hfov.tan();
// To find the viewing width we must multiply by 2d now
w_over_2d * 2.0 * distance
};
let aspect_ratio = self.image_width as f64 / self.image_height as f64;
let viewing_height = viewing_width / aspect_ratio;
// Compute viewing window corners
let n = self.view_dir.normalize();
#[rustfmt::skip] // Don't format, or else this line wraps over
let view_window = Rect {
upper_left: self.eye_pos + n * distance - u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
upper_right: self.eye_pos + n * distance + u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
lower_left: self.eye_pos + n * distance - u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
lower_right: self.eye_pos + n * distance + u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
};
view_window
}
/// Create a pixel translation function based on the viewing window of the
/// current scene
pub fn pixel_translation_function(
&self,
distance: f64,
) -> impl Fn(usize, usize) -> Vector3<f64> {
let view_window = self.compute_viewing_window(distance);
let dx = view_window.upper_right - view_window.upper_left;
let pixel_base_x = dx / self.image_width as f64;
let dy = view_window.lower_left - view_window.upper_left;
let pixel_base_y = dy / self.image_height as f64;
// The final function to be returned
move |px: usize, py: usize| {
let x_component = pixel_base_x * px as f64;
let y_component = pixel_base_y * py as f64;
// Without adding this, we would be getting the top-left of the pixel's
// rectangle. We want the center, so add half of the pixel size as
// well.
let center_offset = (pixel_base_x + pixel_base_y) / 2.0;
view_window.upper_left + x_component + y_component + center_offset
}
}
}

View file

@ -0,0 +1,276 @@
use std::iter;
use nalgebra::Vector3;
use ordered_float::NotNan;
use rand::Rng;
use rayon::prelude::{
IndexedParallelIterator, IntoParallelIterator, IntoParallelRefIterator,
ParallelIterator,
};
use crate::{image::Color, ray::Ray, utils::dot};
use super::{
data::{DepthCueing, Light, LightKind, Object},
Scene,
};
impl Scene {
/// Determine the color that should be used to fill this pixel.
///
/// - material_idx is the index into the materials list.
/// - intersection_context contains information on vectors where the
/// intersection occurred
///
/// Also known as Shade_Ray in the slides.
pub fn compute_pixel_color(
&self,
obj_idx: usize,
material_idx: usize,
intersection_context: IntersectionContext,
) -> Color {
// TODO: Does it make sense to make this function fallible from an API
// design standpoint?
let material = match self.materials.get(material_idx) {
Some(v) => v,
None => return self.bkg_color,
};
let ambient_component = material.k_a * material.diffuse_color;
// Diffuse and specular lighting for each separate light
let diffuse_and_specular: Vector3<f64> = self
.lights
.par_iter()
.map(|light| {
// The vector pointing in the direction of the light
let light_direction = light.direction_from(intersection_context.point);
let normal = intersection_context.normal.normalize();
let viewer_direction = self.eye_pos - intersection_context.point;
let halfway_direction =
((light_direction + viewer_direction) / 2.0).normalize();
let diffuse_component = material.k_d
* material.diffuse_color
* dot(normal, light_direction).max(0.0);
let specular_component = material.k_s
* material.specular_color
* dot(normal, halfway_direction)
.max(0.0)
.powf(material.exponent);
// Shadow coefficient between 0 and 1 to control how bright this pixel
// should be from being in the shadow of another object (could be
// between 0 and 1 when applying soft shadows)
let shadow_coefficient = self.compute_shadow_coefficient(
obj_idx,
intersection_context.point,
light,
);
let attenuation_coefficient = match &light.kind {
LightKind::Point {
location,
attenuation: Some(att),
} => {
let dist = (location - intersection_context.point).norm();
let denom = att.c1 + att.c2 * dist + att.c3 * dist.powi(2);
if denom == 0.0 {
warn!("Light attenuation coefficients produced a denominator of 0. Check your inputs...");
1.0 // Some kind of graceful fallback here
} else {
1.0 / denom
}
}
_ => 1.0,
};
let diffuse_and_specular = diffuse_component + specular_component;
attenuation_coefficient
* shadow_coefficient
* light.color.component_mul(&diffuse_and_specular)
})
.sum();
let color = ambient_component + diffuse_and_specular;
// Apply depth cueing to the result
let a_dc = {
// Distance from the viewer
let d_obj = (intersection_context.point - self.eye_pos).norm();
let DepthCueing {
dist_max,
dist_min,
a_max,
a_min,
..
} = self.depth_cueing;
if d_obj < dist_min {
a_max
} else if d_obj < dist_max {
a_min + (a_max - a_min) * (dist_max - d_obj) / (dist_max - dist_min)
} else {
a_min
}
};
let color = a_dc * color + (1.0 - a_dc) * self.depth_cueing.color;
// Need to clamp the result so none of the components goes over 1
let clamped_result = color.map(|v| v.min(1.0));
clamped_result
}
/// Perform another ray casting to see if there are any objects obstructing
/// the light source to this particular point
pub fn compute_shadow_coefficient(
&self,
obj_idx: usize,
point: Vector3<f64>,
light: &Light,
) -> f64 {
let light_direction = light.direction_from(point);
let ray = Ray {
origin: point,
direction: light_direction.normalize(),
};
// Small helper for iterating over all of the objects in the scene except
// for the current one
let other_objects = self
.objects
.par_iter()
.enumerate()
.filter(|(i, _)| *i != obj_idx);
// Get the list of intersections with all the other objects in the scene
// This list will be a set of opacities
let intersections = other_objects
.filter_map(|(_, object)| {
let intersection_context = match object.kind.intersects_ray_at(&ray) {
Ok(v) => v,
Err(err) => {
error!("Error while performing shadow casting: {err}");
None
}
}?;
let intersection_time = *intersection_context.time;
match light.kind {
// In the case of point lights, we must check to see if both t > 0 and
// t is less than the time it took to even get to the light.
LightKind::Point { location, .. } => {
let light_time = (location - ray.origin).norm();
if intersection_time <= 0.0 || intersection_time >= light_time {
None
} else {
let soft_shadow_coefficient =
self.compute_soft_shadow_coefficient(location, point, object);
Some(soft_shadow_coefficient)
}
}
// In the case of directional lights, only t > 0 needs to be checked,
// otherwise
LightKind::Directional { .. } => {
if intersection_time <= 0.0 {
None
} else {
Some(0.0) // complete obstruction
}
}
}
})
.collect::<Vec<_>>();
let average =
intersections.iter().cloned().sum::<f64>() / intersections.len() as f64;
match intersections.is_empty() {
true => 1.0,
false => average,
}
}
fn compute_soft_shadow_coefficient(
&self,
light_location: Vector3<f64>,
original_intersection_point: Vector3<f64>,
object: &Object,
) -> f64 {
// Soft shadows: jitter some rays here to somewhere close to the
// actual location as well, and measure the proportion
// of them that intersect any objects
const JITTER_RADIUS: f64 = 1.0;
const JITTER_RAYS: usize = 75;
let mut rng = rand::thread_rng();
let locations = iter::repeat_with(|| {
let x = rng.gen_range(0.0..JITTER_RADIUS);
let y = rng.gen_range(0.0..JITTER_RADIUS);
let z = rng.gen_range(0.0..JITTER_RADIUS);
let delta = Vector3::new(x, y, z);
light_location + delta
})
.take(JITTER_RAYS)
.collect::<Vec<_>>();
let num_obstructed_rays = locations
.into_par_iter()
.filter(|location| {
let direction = (location - original_intersection_point).normalize();
let ray = Ray {
origin: original_intersection_point,
direction,
};
let intersection_context = match object.kind.intersects_ray_at(&ray) {
Ok(Some(v)) => v,
Ok(None) => return false,
Err(err) => {
error!("Error while performing shadow casting: {err}");
return false;
}
};
let light_time = (location - ray.origin).norm();
let intersection_time = *intersection_context.time;
0.0 < intersection_time && intersection_time < light_time
})
.count();
(JITTER_RAYS - num_obstructed_rays) as f64 / JITTER_RAYS as f64
}
}
/// Information about an intersection
#[derive(Derivative)]
#[derivative(Debug, PartialEq, PartialOrd, Ord)]
pub struct IntersectionContext {
/// The time of the intersection in the parametric ray
///
/// Unfortunately, IEEE floats in Rust don't have total ordering, because
/// NaNs violate ordering properties. The way to remedy this is to ensure we
/// don't have NaNs by wrapping it into this type, which then implements
/// total ordering.
pub time: NotNan<f64>,
/// The intersection point.
#[derivative(PartialEq = "ignore", Ord = "ignore")]
pub point: Vector3<f64>,
/// The normal vector protruding from the surface of the object at the
/// intersection point
#[derivative(PartialEq = "ignore", Ord = "ignore")]
pub normal: Vector3<f64>,
}
impl Eq for IntersectionContext {}
impl IntersectionContext {}

View file

@ -0,0 +1,191 @@
use std::{fs::File, io::Read, path::Path};
use anyhow::Result;
use nalgebra::Vector3;
use crate::scene::{
cylinder::Cylinder,
data::{Attenuation, Light, LightKind, Material, Object},
sphere::Sphere,
Scene,
};
use super::data::{DepthCueing, ObjectKind};
impl Scene {
/// Parse the input file into a scene
pub fn from_input_file(path: impl AsRef<Path>) -> Result<Self> {
// Scope the read so the file is dropped and closed immediately after the
// contents have been read to memory
let contents = {
let mut contents = String::new();
let mut file = File::open(path.as_ref())?;
file.read_to_string(&mut contents)?;
contents
};
let mut scene = Scene::default();
let mut material_color = None;
for line in contents.lines() {
let mut parts = line.split_whitespace();
let keyword = match parts.next() {
Some(v) => v,
None => continue,
};
if keyword == "imsize" {
let parts = parts
.map(|s| s.parse::<usize>().map_err(|e| e.into()))
.collect::<Result<Vec<_>>>()?;
if let [width, height] = parts[..] {
scene.image_width = width;
scene.image_height = height;
}
} else if keyword == "projection" {
if let Some("parallel") = parts.next() {
scene.parallel_projection = true;
}
}
// Do float parsing instead
else {
let parts = parts
.map(|s| s.parse::<f64>().map_err(|e| e.into()))
.collect::<Result<Vec<_>>>()?;
let read_vec3 = |start: usize| {
ensure!(parts.len() >= start + 3, "Vec3 requires 3 components.");
Ok(Vector3::new(
parts[start],
parts[start + 1],
parts[start + 2],
))
};
match keyword {
"eye" => scene.eye_pos = read_vec3(0)?,
"viewdir" => scene.view_dir = read_vec3(0)?,
"updir" => scene.up_dir = read_vec3(0)?,
"hfov" => scene.hfov = parts[0],
"bkgcolor" => scene.bkg_color = read_vec3(0)?,
// light x y z w r g b
"light" => {
ensure!(parts.len() == 7, "Light requires 7 params");
let kind = match parts[3] as usize {
0 => LightKind::Directional {
direction: read_vec3(0)?,
},
1 => LightKind::Point {
location: read_vec3(0)?,
attenuation: None,
},
_ => bail!("Invalid w; must be either 0 or 1"),
};
let light = Light {
kind,
color: read_vec3(4)?,
};
scene.lights.push(light);
}
// attlight x y z w r g b c1 c2 c3
"attlight" => {
ensure!(parts.len() == 10, "Attenuated light requires 10 params");
let kind = match parts[3] as usize {
// TODO: Is this even defined? Pending TA answer
0 => LightKind::Directional {
direction: read_vec3(0)?,
},
1 => LightKind::Point {
location: read_vec3(0)?,
attenuation: Some(Attenuation {
c1: parts[7],
c2: parts[8],
c3: parts[9],
}),
},
_ => bail!("Invalid w; must be either 0 or 1"),
};
let light = Light {
kind,
color: read_vec3(4)?,
};
scene.lights.push(light);
}
// depthcueing dcr dcg dcb amax amin distmax distmin
"depthcueing" => {
ensure!(parts.len() == 7, "Depth cueing requires 7 params");
let color = read_vec3(0)?;
scene.depth_cueing = DepthCueing {
color,
a_max: parts[3],
a_min: parts[4],
dist_max: parts[5],
dist_min: parts[6],
};
}
// mtlcolor Odr Odg Odb Osr Osg Osb ka kd ks n
"mtlcolor" => {
ensure!(parts.len() == 10, "Material color requires 10 params");
let diffuse_color = read_vec3(0)?;
let specular_color = read_vec3(3)?;
let material = Material {
diffuse_color,
specular_color,
k_a: parts[6],
k_d: parts[7],
k_s: parts[8],
exponent: parts[9],
};
let idx = scene.materials.len();
material_color = Some(idx);
scene.materials.push(material);
}
"sphere" => scene.objects.push(Object {
kind: ObjectKind::Sphere(Sphere {
center: read_vec3(0)?,
radius: parts[3],
}),
material: match material_color {
Some(v) => v,
None => {
bail!("Each sphere must be preceded by a `mtlcolor` line")
}
},
}),
"cylinder" => scene.objects.push(Object {
kind: ObjectKind::Cylinder(Cylinder {
center: read_vec3(0)?,
direction: read_vec3(3)?,
radius: parts[6],
length: parts[7],
}),
material: match material_color {
Some(v) => v,
None => {
bail!("Each sphere must be preceded by a `mtlcolor` line")
}
},
}),
_ => bail!("Unknown keyword {keyword}"),
}
}
}
Ok(scene)
}
}

View file

@ -0,0 +1,34 @@
pub mod cylinder;
pub mod data;
pub mod illumination;
pub mod input_file;
pub mod sphere;
use nalgebra::Vector3;
use crate::image::Color;
use self::data::{DepthCueing, Light, Material, Object, Attenuation};
#[derive(Debug, Default)]
pub struct Scene {
pub eye_pos: Vector3<f64>,
pub view_dir: Vector3<f64>,
pub up_dir: Vector3<f64>,
/// Horizontal field of view (in degrees)
pub hfov: f64,
pub parallel_projection: bool,
pub image_width: usize,
pub image_height: usize,
/// Background color
pub bkg_color: Color,
pub depth_cueing: DepthCueing,
pub attenuation: Attenuation,
pub materials: Vec<Material>,
pub lights: Vec<Light>,
pub objects: Vec<Object>,
}

View file

@ -0,0 +1,74 @@
use anyhow::Result;
use nalgebra::Vector3;
use ordered_float::NotNan;
use crate::{ray::Ray, utils::min_f64};
use super::illumination::IntersectionContext;
#[derive(Debug)]
pub struct Sphere {
pub center: Vector3<f64>,
pub radius: f64,
}
impl Sphere {
/// Given a sphere, returns the first time at which this ray intersects the
/// sphere.
///
/// If there is no intersection point, returns None.
pub fn intersects_ray_at(
&self,
ray: &Ray,
) -> Result<Option<IntersectionContext>> {
let a = ray.direction.norm();
let b = 2.0
* (ray.direction.x * (ray.origin.x - self.center.x)
+ ray.direction.y * (ray.origin.y - self.center.y)
+ ray.direction.z * (ray.origin.z - self.center.z));
let c = (ray.origin.x - self.center.x).powi(2)
+ (ray.origin.y - self.center.y).powi(2)
+ (ray.origin.z - self.center.z).powi(2)
- self.radius.powi(2);
let discriminant = b * b - 4.0 * a * c;
let time = match discriminant {
// Discriminant < 0, means the equation has no solutions.
d if d < 0.0 => None,
// Discriminant == 0
d if d == 0.0 => Some(-b / (2.0 * a)),
d if d > 0.0 => {
let solution_1 = (-b + discriminant.sqrt()) / (2.0 * a);
let solution_2 = (-b - discriminant.sqrt()) / (2.0 * a);
let solutions = [solution_1, solution_2]
.into_iter()
// Remove any t < 0, since that means it's behind the viewer and we
// can't see it.
.filter(|t| *t >= 0.0);
// Return the minimum solution
min_f64(solutions)
}
// Probably hit some NaN or Infinity value due to faulty inputs...
_ => unreachable!("Invalid determinant value: {discriminant}"),
};
let time = match time.and_then(|t| NotNan::new(t).ok()) {
Some(v) => v,
None => return Ok(None),
};
let point = ray.eval(*time);
let normal = (point - self.center).normalize();
Ok(Some(IntersectionContext {
time,
point,
normal,
}))
}
}

View file

@ -0,0 +1,93 @@
use anyhow::Result;
use nalgebra::{Matrix3, Vector3};
use ordered_float::NotNan;
/// Finds the minimum of an iterator of f64s, ignoring any NaN values
#[inline]
pub fn min_f64<I>(i: I) -> Option<f64>
where
I: Iterator<Item = f64>,
{
i.filter_map(|i| NotNan::new(i).ok())
.min()
.map(|i| i.into_inner())
}
/// Finds the minimum of an iterator of f64s using the given predicate, ignoring
/// any NaN values
#[inline]
pub fn min_f64_by_key<I, F>(i: I, f: F) -> Option<f64>
where
I: Iterator<Item = f64>,
F: FnMut(&NotNan<f64>),
{
i.filter_map(|i| NotNan::new(i).ok())
.min_by_key(f)
.map(|i| i.into_inner())
}
/// Dot-product between two 3D vectors.
#[inline]
pub fn dot(a: Vector3<f64>, b: Vector3<f64>) -> f64 {
a.x * b.x + a.y * b.y + a.z * b.z
}
/// Cross-product between two 3D vectors.
#[inline]
pub fn cross(a: Vector3<f64>, b: Vector3<f64>) -> Vector3<f64> {
let x = a.y * b.z - a.z * b.y;
let y = a.z * b.x - a.x * b.z;
let z = a.x * b.y - a.y * b.x;
Vector3::new(x, y, z)
}
/// Calculate the rotation matrix between the 2 given vectors
///
/// Based on the method given [here][1].
///
/// [1]: https://math.stackexchange.com/a/897677
pub fn compute_rotation_matrix(
a: Vector3<f64>,
b: Vector3<f64>,
) -> Result<Matrix3<f64>> {
// Special case: if a and b are in the same direction, just return the
// identity matrix.
if a.normalize() == b.normalize() {
return Ok(Matrix3::identity());
}
let cos_t = dot(a, b);
let sin_t = cross(a, b).norm();
let g = Matrix3::new(cos_t, -sin_t, 0.0, sin_t, cos_t, 0.0, 0.0, 0.0, 1.0);
// New basis vectors
let u = a;
let v = (b - cos_t * a).normalize();
let w = cross(b, a);
// Not sure if this is required to be invertible?
let f_inverse = Matrix3::from_columns(&[u, v, w]);
let f = match f_inverse.try_inverse() {
Some(v) => v,
None => {
// So I ran into this case trying to compute the rotation matrix where one
// of the vector endpoints was (0, 0, 0). I'm pretty sure this case makes
// no sense in reality, which means if I ever encounter this case, I
// probably made a mistake somewhere before. So going to just error
// out here and screw recovering.
//
// println!("Failed to compute inverse matrix.");
// println!("- Initial: a = {a}, b = {b}");
// println!("- cos(t) = {cos_t}, sin(t) = {sin_t}");
// println!("- Basis: u = {u}, v = {v}, w = {w}");
bail!("Failed to compute inverse matrix of {f_inverse}\na = {a}\nb = {b}")
}
};
// if (f_inverse * g * f).norm() != 1.0 {
// bail!("WTF {}", (f_inverse * g * f).norm());
// }
Ok(f_inverse * g * f)
}

173
assignment-1b/writeup.md Normal file
View file

@ -0,0 +1,173 @@
---
geometry: margin=2cm
output: pdf_document
---
# Raytracer part B
This project implements a raytracer with Blinn-Phong illumination and shadows
implemented. The primary formula that is used by this implementation is:
\begin{equation}
I_{\lambda} =
k_a O_{d\lambda} +
\sum_{i=1}^{n_\textrm{lights}} \left(
f_\textrm{att} \cdot
S_i \cdot
IL_{i\lambda} \left[
k_d O_{d\lambda} \max ( 0, \vec{N} \cdot \vec{L_i} ) +
k_s O_{s\lambda} \max ( 0, \vec{N} \cdot \vec{H_i} )^n
\right]
\right)
\end{equation}
Where:
- $I_{\lambda}$ is the final illumination of the pixel on an object
- $k_a$ is the material's ambient reflectivity
- $k_d$ is the material's diffuse reflectivity
- $k_s$ is the material's specular reflectivity
- $n_\textrm{lights}$ is the number of lights
- $f_\textrm{att}$ is the light attenuation factor (1.0 if attenuation is not on)
- $S_i$ is the shadow coefficient for light $i$
- $IL_{i\lambda}$ is the intensity of light $i$
- $O_{d\lambda}$ is the object's diffuse color
- $O_{s\lambda}$ is the object's specular color
- $\vec{N}$ is the normal vector to the object's surface
- $\vec{L_i}$ is the direction from the intersection point to the light $i$
- $\vec{H_i}$ is halfway between the direction to the light $i$ and the
direction to the viewer
- $n$ is the exponent for the specular component
In this report we will look through how these various factors influence the
rendering of the scene. All the images along with their source `.txt` files,
rendered `.ppm` files, and converted `.png` files can be found in the `examples`
directory of this handin.
## Varying $k_a$
$k_a$ is the strength of ambient light. It's used as a coefficient for the
object's diffuse color, which keeps a constant value independent of the
positions of the object, light, and the viewer. In the image below, I varied
$k_a$ between 0.2 and 1. Note how the overall color of the ball increases or
decreases in brightness when all other factors remain constant.
![Varying $k_a$](examples/ka-demo.png){width=360px}
\
## Varying $k_d$
$k_d$ is the strength of the diffuse component. It also affects an object's
diffuse color, but at a strength that's affected by how much of it faces the
light. Much like the dark side of the moon, the parts of the object that aren't
pointed at the light will not receive as much of the light's influence. In the
image below, I varied $k_d$ between 0.2 and 1. Note how the part pointed to the
light changes the strength of the brightness as all other factors remain
constant.
![Varying $k_d$](examples/kd-demo.png){width=360px}
\
## Varying $k_s$
$k_s$ is the specular strength. It uses the object's specular color, which is
like its reflective component. When there is a large specular $k_s$, there's a
shine that appears on the object with a greater intensity. In the image below, I
varied $k_s$ between 0.2 and 1. Note how the whiteness of the light is more
reflective in higher $k_s$ values as other factors remain constant.
![Varying $k_s$](examples/ks-demo.png){width=360px}
\
## Varying $n$
$n$ is the exponent saying how big the radius of the specular highlight should
be. In the equation, increasing the exponent usually leads to smaller shines. In
the image below, I varied $n$ between 2 and 100. Note how the size of the shine
is the same intensity, but more focused but covers a smaller area as $n$
increases.
![Varying $n$](examples/n-demo.png){width=360px}
\
## Multiple lights
Multiple lights are handled by multiplying each light against an intensity
level, and then added together. Unfortunately, this means that the intensity of
each light can't be too bright. We rely on the image to not use lights that are
too bright. Because this may result in color values above 1.0, the final value
is clamped against 1.0. Below is an example of a scene with two lights; one to
the left and one to the right:
![Multiple lights](examples/multiple-lights-demo.png){width=360px}
\
## Shadows
Shadows are implemented by pointing a second ray between the intersection point
of the original view ray and each light. If the light has something obstructing
it in the middle, the light's effect is not used.
The soft shadow effect is realized by jittering rays across an area. In my
implementation, a jitter radius of about 1.0 is used, and 75 rays are shot into
uniformly sampled points within that radius. This also has the side effect that
rays that are closer to the original ray are sampled more frequently. Each of
these rays produces either 0 or 1 depending on if it was obstructed by the
object. Taking the proportion of rays that hit as a coefficient for the shadow,
we can get some soft shadow effects like this:
![Soft shadows](examples/soft-shadow-demo.png){width=360px}
\
## Light attenuation
Light attenuation is when more of the light is applied for objects that are
closer to a particular light source. The function that's applied is an inverse
quadratic formula with respect to the distance the object is from the light:
\begin{equation}
f_\textrm{att}(d) = \frac{1}{c_1 + c_2 d + c_3 d^2}
\end{equation}
Where:
- $f_\textrm{att}$ is the attenuation factor
- $d$ is the distance the object is from the light
- $c_1$, $c_2$, and $c_3$ are user-supplied coefficients
As you can see below, the effect of the light drops off with the distance from
the light (light coming from the left):
![Light attenuation](examples/attenuation-demo.png){width=360px}
\
## Depth Cueing
Depth cueing is when the objects further from the viewer have a lower opacity to
"fade" into the background in some sense. A good example of this can be seen in
the image below; note how the objects are less and less bright the further they
are away from the eye.
![Depth cueing](examples/depth-cueing-demo.png){width=360px}
\
## Shortcomings of the model
The Phong formula is just a model of how light works, and doesn't actually
represent reality. There's not actually rays physically escaping our eyes and
hitting objects; it's actually the other way around, but computing it that way
would not be efficient since we would be factoring in a lot of rays that don't
ever get rendered.
Also, one needs to take care to use reasonable constants. For example, if using
a different specular light color than the diffuse color, then it may produce
some bizarre lighting effects that may not actually look right compare to
reality.
# Arbitrary Objects
Here is an example scene with some objects that demonstrates some of the
features of the raytracer.
![Objects in the scene](examples/objects.png){width=360px}
\

10
assignment-1c/.gitignore vendored Normal file
View file

@ -0,0 +1,10 @@
/target
/assignment-1c
/raytracer1c
/examples/*.png
*.ppm
*.zip
*.pdf
perf.data*
flamegraph.svg
showcase.png

View file

980
assignment-1c/Cargo.lock generated Normal file
View file

@ -0,0 +1,980 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "addr2line"
version = "0.19.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a76fd60b23679b7d19bd066031410fb7e458ccc5e958eb5c325888ce4baedc97"
dependencies = [
"gimli",
]
[[package]]
name = "adler"
version = "1.0.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe"
[[package]]
name = "anyhow"
version = "1.0.68"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2cb2f989d18dd141ab8ae82f64d1a8cdd37e0840f73a406896cf5e99502fab61"
dependencies = [
"backtrace",
]
[[package]]
name = "approx"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cab112f0a86d568ea0e627cc1d6be74a1e9cd55214684db5561995f6dad897c6"
dependencies = [
"num-traits",
]
[[package]]
name = "assignment-1c"
version = "0.1.0"
dependencies = [
"anyhow",
"base64",
"clap",
"derivative",
"generator",
"itertools",
"nalgebra",
"num",
"ordered-float",
"rand",
"rayon",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "autocfg"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa"
[[package]]
name = "backtrace"
version = "0.3.67"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "233d376d6d185f2a3093e58f283f60f880315b6c60075b01f36b3b85154564ca"
dependencies = [
"addr2line",
"cc",
"cfg-if",
"libc",
"miniz_oxide",
"object",
"rustc-demangle",
]
[[package]]
name = "base64"
version = "0.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a4a4ddaa51a5bc52a6948f74c06d20aaaddb71924eab79b8c97a8c556e942d6a"
[[package]]
name = "bitflags"
version = "1.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a"
[[package]]
name = "bytemuck"
version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c041d3eab048880cb0b86b256447da3f18859a163c3b8d8893f4e6368abe6393"
[[package]]
name = "cc"
version = "1.0.79"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50d30906286121d95be3d479533b458f87493b30a4b5f79a607db8f5d11aa91f"
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "clap"
version = "4.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f13b9c79b5d1dd500d20ef541215a6423c75829ef43117e1b4d17fd8af0b5d76"
dependencies = [
"bitflags",
"clap_derive",
"clap_lex",
"is-terminal",
"once_cell",
"strsim",
"termcolor",
]
[[package]]
name = "clap_derive"
version = "4.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "684a277d672e91966334af371f1a7b5833f9aa00b07c84e92fbce95e00208ce8"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "clap_lex"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "783fe232adfca04f90f56201b26d79682d4cd2625e0bc7290b95123afe558ade"
dependencies = [
"os_str_bytes",
]
[[package]]
name = "crossbeam-channel"
version = "0.5.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2dd04ddaf88237dc3b8d8f9a3c1004b506b54b3313403944054d23c0870c521"
dependencies = [
"cfg-if",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "715e8152b692bba2d374b53d4875445368fdf21a94751410af607a5ac677d1fc"
dependencies = [
"cfg-if",
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "01a9af1f4c2ef74bb8aa1f7e19706bc72d03598c8a570bb5de72243c7a9d9d5a"
dependencies = [
"autocfg",
"cfg-if",
"crossbeam-utils",
"memoffset",
"scopeguard",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4fb766fa798726286dbbb842f174001dab8abc7b627a1dd86e0b7222a95d929f"
dependencies = [
"cfg-if",
]
[[package]]
name = "derivative"
version = "2.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fcc3dd5e9e9c0b295d6e1e4d811fb6f157d5ffd784b8d202fc62eac8035a770b"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "either"
version = "1.8.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
[[package]]
name = "errno"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1"
dependencies = [
"errno-dragonfly",
"libc",
"winapi",
]
[[package]]
name = "errno-dragonfly"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf"
dependencies = [
"cc",
"libc",
]
[[package]]
name = "generator"
version = "0.7.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d266041a359dfa931b370ef684cceb84b166beb14f7f0421f4a6a3d0c446d12e"
dependencies = [
"cc",
"libc",
"log",
"rustversion",
"windows",
]
[[package]]
name = "getrandom"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c05aeb6a22b8f62540c194aac980f2115af067bfe15a0734d7277a768d396b31"
dependencies = [
"cfg-if",
"libc",
"wasi",
]
[[package]]
name = "gimli"
version = "0.27.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad0a93d233ebf96623465aad4046a8d3aa4da22d4f4beba5388838c8a434bbb4"
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ee512640fe35acbfb4bb779db6f0d80704c2cacfa2e39b601ef3e3f47d1ae4c7"
dependencies = [
"libc",
]
[[package]]
name = "io-lifetimes"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e7d6c6f8c91b4b9ed43484ad1a938e393caf35960fce7f82a040497207bd8e9e"
dependencies = [
"libc",
"windows-sys",
]
[[package]]
name = "is-terminal"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "28dfb6c8100ccc63462345b67d1bbc3679177c75ee4bf59bf29c8b1d110b8189"
dependencies = [
"hermit-abi",
"io-lifetimes",
"rustix",
"windows-sys",
]
[[package]]
name = "itertools"
version = "0.10.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
dependencies = [
"either",
]
[[package]]
name = "lazy_static"
version = "1.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
[[package]]
name = "libc"
version = "0.2.139"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "201de327520df007757c1f0adce6e827fe8562fbc28bfd9c15571c66ca1f5f79"
[[package]]
name = "linux-raw-sys"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f051f77a7c8e6957c0696eac88f26b0117e54f52d3fc682ab19397a8812846a4"
[[package]]
name = "log"
version = "0.4.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "abb12e687cfb44aa40f41fc3978ef76448f9b6038cad6aef4259d3c095a2382e"
dependencies = [
"cfg-if",
]
[[package]]
name = "matrixmultiply"
version = "0.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "add85d4dd35074e6fedc608f8c8f513a3548619a9024b751949ef0e8e45a4d84"
dependencies = [
"rawpointer",
]
[[package]]
name = "memchr"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d"
[[package]]
name = "memoffset"
version = "0.7.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4"
dependencies = [
"autocfg",
]
[[package]]
name = "miniz_oxide"
version = "0.6.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b275950c28b37e794e8c55d88aeb5e139d0ce23fdbbeda68f8d7174abdf9e8fa"
dependencies = [
"adler",
]
[[package]]
name = "nalgebra"
version = "0.32.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f6515c882ebfddccaa73ead7320ca28036c4bc84c9bcca3cc0cbba8efe89223a"
dependencies = [
"approx",
"matrixmultiply",
"nalgebra-macros",
"num-complex",
"num-rational",
"num-traits",
"simba",
"typenum",
]
[[package]]
name = "nalgebra-macros"
version = "0.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d232c68884c0c99810a5a4d333ef7e47689cfd0edc85efc9e54e1e6bf5212766"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "nu-ansi-term"
version = "0.46.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a8165726e8236064dbb45459242600304b42a5ea24ee2948e18e023bf7ba84"
dependencies = [
"overload",
"winapi",
]
[[package]]
name = "num"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "43db66d1170d347f9a065114077f7dccb00c1b9478c89384490a3425279a4606"
dependencies = [
"num-bigint",
"num-complex",
"num-integer",
"num-iter",
"num-rational",
"num-traits",
]
[[package]]
name = "num-bigint"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f93ab6289c7b344a8a9f60f88d80aa20032336fe78da341afc91c8a2341fc75f"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-complex"
version = "0.4.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "02e0d21255c828d6f128a1e41534206671e8c3ea0c62f32291e808dc82cff17d"
dependencies = [
"num-traits",
"serde",
]
[[package]]
name = "num-integer"
version = "0.1.45"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "225d3389fb3509a24c93f5c29eb6bde2586b98d9f016636dff58d7c6f7569cd9"
dependencies = [
"autocfg",
"num-traits",
]
[[package]]
name = "num-iter"
version = "0.1.43"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7d03e6c028c5dc5cac6e2dec0efda81fc887605bb3d884578bb6d6bf7514e252"
dependencies = [
"autocfg",
"num-integer",
"num-traits",
]
[[package]]
name = "num-rational"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0638a1c9d0a3c0914158145bc76cff373a75a627e6ecbfb71cbe6f453a5a19b0"
dependencies = [
"autocfg",
"num-bigint",
"num-integer",
"num-traits",
"serde",
]
[[package]]
name = "num-traits"
version = "0.2.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "578ede34cf02f8924ab9447f50c28075b4d3e5b269972345e7e0372b38c6cdcd"
dependencies = [
"autocfg",
]
[[package]]
name = "num_cpus"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0fac9e2da13b5eb447a6ce3d392f23a29d8694bff781bf03a16cd9ac8697593b"
dependencies = [
"hermit-abi",
"libc",
]
[[package]]
name = "object"
version = "0.30.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ea86265d3d3dcb6a27fc51bd29a4bf387fae9d2986b823079d4986af253eb439"
dependencies = [
"memchr",
]
[[package]]
name = "once_cell"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
[[package]]
name = "ordered-float"
version = "3.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d84eb1409416d254e4a9c8fa56cc24701755025b458f0fcd8e59e1f5f40c23bf"
dependencies = [
"num-traits",
]
[[package]]
name = "os_str_bytes"
version = "6.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b7820b9daea5457c9f21c69448905d723fbd21136ccf521748f23fd49e723ee"
[[package]]
name = "overload"
version = "0.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b15813163c1d831bf4a13c3610c05c0d03b39feb07f7e09fa234dac9b15aaf39"
[[package]]
name = "paste"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d01a5bd0424d00070b0098dd17ebca6f961a959dead1dbcbbbc1d1cd8d3deeba"
[[package]]
name = "pin-project-lite"
version = "0.2.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e0a7ae3ac2f1173085d398531c705756c94a4c56843785df85a60c1a0afac116"
[[package]]
name = "ppv-lite86"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b40af805b3121feab8a3c29f04d8ad262fa8e0561883e7653e024ae4479e6de"
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.50"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6ef7d57beacfaf2d8aee5937dab7b7f28de3cb8b1828479bb5de2a7106f2bae2"
dependencies = [
"unicode-ident",
]
[[package]]
name = "quote"
version = "1.0.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8856d8364d252a14d474036ea1358d63c9e6965c8e5c1885c18f73d70bff9c7b"
dependencies = [
"proc-macro2",
]
[[package]]
name = "rand"
version = "0.8.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
dependencies = [
"libc",
"rand_chacha",
"rand_core",
]
[[package]]
name = "rand_chacha"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
dependencies = [
"ppv-lite86",
"rand_core",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
dependencies = [
"getrandom",
]
[[package]]
name = "rawpointer"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "60a357793950651c4ed0f3f52338f53b2f809f32d83a07f72909fa13e4c6c1e3"
[[package]]
name = "rayon"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6db3a213adf02b3bcfd2d3846bb41cb22857d131789e01df434fb7e7bc0759b7"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.10.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "356a0625f1954f730c0201cdab48611198dc6ce21f4acff55089b5a78e6e835b"
dependencies = [
"crossbeam-channel",
"crossbeam-deque",
"crossbeam-utils",
"num_cpus",
]
[[package]]
name = "rustc-demangle"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ef03e0a2b150c7a90d01faf6254c9c48a41e95fb2a8c2ac1c6f0d2b9aefc342"
[[package]]
name = "rustix"
version = "0.36.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4fdebc4b395b7fbb9ab11e462e20ed9051e7b16e42d24042c776eca0ac81b03"
dependencies = [
"bitflags",
"errno",
"io-lifetimes",
"libc",
"linux-raw-sys",
"windows-sys",
]
[[package]]
name = "rustversion"
version = "1.0.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5583e89e108996506031660fe09baa5011b9dd0341b89029313006d1fb508d70"
[[package]]
name = "safe_arch"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "794821e4ccb0d9f979512f9c1973480123f9bd62a90d74ab0f9426fcf8f4a529"
dependencies = [
"bytemuck",
]
[[package]]
name = "scopeguard"
version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
[[package]]
name = "serde"
version = "1.0.152"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb7d1f0d3021d347a83e556fc4683dea2ea09d87bccdf88ff5c12545d89d5efb"
[[package]]
name = "sharded-slab"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "900fba806f70c630b0a382d0d825e17a0f19fcd059a2ade1ff237bcddf446b31"
dependencies = [
"lazy_static",
]
[[package]]
name = "simba"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "50582927ed6f77e4ac020c057f37a268fc6aebc29225050365aacbb9deeeddc4"
dependencies = [
"approx",
"num-complex",
"num-traits",
"paste",
"wide",
]
[[package]]
name = "smallvec"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a507befe795404456341dfab10cef66ead4c041f62b8b11bbb92bffe5d0953e0"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "syn"
version = "1.0.107"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1f4064b5b16e03ae50984a5a8ed5d4f8803e6bc1fd170a3cda91a1be4b18e3f5"
dependencies = [
"proc-macro2",
"quote",
"unicode-ident",
]
[[package]]
name = "termcolor"
version = "1.2.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "be55cf8942feac5c765c2c993422806843c9a9a45d4d5c407ad6dd2ea95eb9b6"
dependencies = [
"winapi-util",
]
[[package]]
name = "thread_local"
version = "1.1.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3fdd6f064ccff2d6567adcb3873ca630700f00b5ad3f060c25b5dcfd9a4ce152"
dependencies = [
"cfg-if",
"once_cell",
]
[[package]]
name = "tracing"
version = "0.1.37"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ce8c33a8d48bd45d624a6e523445fd21ec13d3653cd51f681abf67418f54eb8"
dependencies = [
"cfg-if",
"pin-project-lite",
"tracing-attributes",
"tracing-core",
]
[[package]]
name = "tracing-attributes"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4017f8f45139870ca7e672686113917c71c7a6e02d4924eda67186083c03081a"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "tracing-core"
version = "0.1.30"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24eb03ba0eab1fd845050058ce5e616558e8f8d8fca633e6b163fe25c797213a"
dependencies = [
"once_cell",
"valuable",
]
[[package]]
name = "tracing-log"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ddad33d2d10b1ed7eb9d1f518a5674713876e97e5bb9b7345a7984fbb4f922"
dependencies = [
"lazy_static",
"log",
"tracing-core",
]
[[package]]
name = "tracing-subscriber"
version = "0.3.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6176eae26dd70d0c919749377897b54a9276bd7061339665dd68777926b5a70"
dependencies = [
"nu-ansi-term",
"sharded-slab",
"smallvec",
"thread_local",
"tracing-core",
"tracing-log",
]
[[package]]
name = "typenum"
version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "497961ef93d974e23eb6f433eb5fe1b7930b659f06d12dec6fc44a8f554c0bba"
[[package]]
name = "unicode-ident"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "84a22b9f218b40614adcb3f4ff08b703773ad44fa9423e4e0d346d5db86e4ebc"
[[package]]
name = "valuable"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "830b7e5d4d90034032940e4ace0d9a9a057e7a45cd94e6c007832e39edb82f6d"
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wide"
version = "0.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ae41ecad2489a1655c8ef8489444b0b113c0a0c795944a3572a0931cf7d2525c"
dependencies = [
"bytemuck",
"safe_arch",
]
[[package]]
name = "winapi"
version = "0.3.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419"
dependencies = [
"winapi-i686-pc-windows-gnu",
"winapi-x86_64-pc-windows-gnu",
]
[[package]]
name = "winapi-i686-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6"
[[package]]
name = "winapi-util"
version = "0.1.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "70ec6ce85bb158151cae5e5c87f95a8e97d2c0c4b001223f33a334e3ce5de178"
dependencies = [
"winapi",
]
[[package]]
name = "winapi-x86_64-pc-windows-gnu"
version = "0.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f"
[[package]]
name = "windows"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1c4bd0a50ac6020f65184721f758dba47bb9fbc2133df715ec74a237b26794a"
dependencies = [
"windows_aarch64_msvc 0.39.0",
"windows_i686_gnu 0.39.0",
"windows_i686_msvc 0.39.0",
"windows_x86_64_gnu 0.39.0",
"windows_x86_64_msvc 0.39.0",
]
[[package]]
name = "windows-sys"
version = "0.42.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5a3e1820f08b8513f676f7ab6c1f99ff312fb97b553d30ff4dd86f9f15728aa7"
dependencies = [
"windows_aarch64_gnullvm",
"windows_aarch64_msvc 0.42.1",
"windows_i686_gnu 0.42.1",
"windows_i686_msvc 0.42.1",
"windows_x86_64_gnu 0.42.1",
"windows_x86_64_gnullvm",
"windows_x86_64_msvc 0.42.1",
]
[[package]]
name = "windows_aarch64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8c9864e83243fdec7fc9c5444389dcbbfd258f745e7853198f365e3c4968a608"
[[package]]
name = "windows_aarch64_msvc"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec7711666096bd4096ffa835238905bb33fb87267910e154b18b44eaabb340f2"
[[package]]
name = "windows_aarch64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c8b1b673ffc16c47a9ff48570a9d85e25d265735c503681332589af6253c6c7"
[[package]]
name = "windows_i686_gnu"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "763fc57100a5f7042e3057e7e8d9bdd7860d330070251a73d003563a3bb49e1b"
[[package]]
name = "windows_i686_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "de3887528ad530ba7bdbb1faa8275ec7a1155a45ffa57c37993960277145d640"
[[package]]
name = "windows_i686_msvc"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7bc7cbfe58828921e10a9f446fcaaf649204dcfe6c1ddd712c5eebae6bda1106"
[[package]]
name = "windows_i686_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bf4d1122317eddd6ff351aa852118a2418ad4214e6613a50e0191f7004372605"
[[package]]
name = "windows_x86_64_gnu"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6868c165637d653ae1e8dc4d82c25d4f97dd6605eaa8d784b5c6e0ab2a252b65"
[[package]]
name = "windows_x86_64_gnu"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1040f221285e17ebccbc2591ffdc2d44ee1f9186324dd3e84e99ac68d699c45"
[[package]]
name = "windows_x86_64_gnullvm"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "628bfdf232daa22b0d64fdb62b09fcc36bb01f05a3939e20ab73aaf9470d0463"
[[package]]
name = "windows_x86_64_msvc"
version = "0.39.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5e4d40883ae9cae962787ca76ba76390ffa29214667a111db9e0a1ad8377e809"
[[package]]
name = "windows_x86_64_msvc"
version = "0.42.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "447660ad36a13288b1db4d4248e857b510e8c3a225c822ba4fb748c0aafecffd"

24
assignment-1c/Cargo.toml Normal file
View file

@ -0,0 +1,24 @@
[package]
name = "assignment-1c"
authors = ["Michael Zhang <zhan4854@umn.edu>"]
version = "0.1.0"
edition = "2021"
[[bin]]
name = "raytracer1c"
path = "src/main.rs"
[dependencies]
anyhow = { version = "1.0.68", features = ["backtrace"] }
base64 = "0.21.0"
clap = { version = "4.1.4", features = ["cargo", "derive"] }
derivative = "2.2.0"
generator = "0.7.2"
itertools = "0.10.5"
nalgebra = "0.32.1"
num = { version = "0.4.0", features = ["serde"] }
ordered-float = "3.4.0"
rand = "0.8.5"
rayon = "1.6.1"
tracing = "0.1.37"
tracing-subscriber = "0.3.16"

46
assignment-1c/Makefile Normal file
View file

@ -0,0 +1,46 @@
.PHONY: all clean
.PRECIOUS: $(EXAMPLES_PPM)
RAYTRACER_FLAGS :=
DOCKER := docker
ZIP := zip
PANDOC := pandoc
CONVERT := convert
HANDIN := ./hw1c.michael.zhang.zip
BINARY := ./raytracer1c
SOURCES := Cargo.toml $(shell find -name "*.rs")
EXAMPLES := $(shell find examples -name "*.txt")
EXAMPLES_PPM := $(patsubst %.txt,%.ppm,$(EXAMPLES))
EXAMPLES_PNG := $(patsubst %.txt,%.png,$(EXAMPLES))
all: $(HANDIN)
$(BINARY): $(SOURCES)
mkdir -p target/docker
$(DOCKER) run \
--rm \
-v "$(shell pwd)":/usr/src/myapp \
-v cargo-registry:/usr/local/cargo \
--user "$(shell id -u)":"$(shell id -g)" \
-w /usr/src/myapp \
-e CARGO_TARGET_DIR=/usr/src/myapp/target/docker \
rust \
cargo build --profile release-handin
mv target/docker/release-handin/raytracer1c $@
$(HANDIN): $(BINARY) Makefile Cargo.toml Cargo.lock README.md $(EXAMPLES_PNG) $(EXAMPLES_PPM)
$(ZIP) -r $@ src examples $^
examples/%.ppm: examples/%.txt $(SOURCES)
cargo run --release -- -o $@ $(RAYTRACER_FLAGS) $<
examples/%.png: examples/%.ppm
convert $< $@
clean:
rm -rf target/docker \
$(HANDIN) $(BINARY) \
$(EXAMPLES_PPM) $(EXAMPLES_PNG)

29
assignment-1c/README.md Normal file
View file

@ -0,0 +1,29 @@
# Raycaster
## Bundle contents
Writeup is located at `/writeup.pdf`.
The binary can be found at `/raytracer1b`. Run `./raytracer1b --help` to see
how to use it. The binary has been built using the Rust Docker image, which
should have an environment similar to CSELabs. If there is trouble running the
binary, try building from source, as documented below.
Examples are found in the `examples` directory. The text files are the input
sources, and the ppm files are the corresponding outputs. They have been
generated by running this program. For convenience, pngs have also been provided
using imagemagick.
## Showcase image
The showcase image can be found at `/showcase.png`.
## Building from source
The Makefile currently uses Docker to produce a more consistent build. If you
have a Rust+Cargo toolchain installed locally, it's also possible to build the
source using just:
cargo build --release
The binary will be found in `target/release`.

2
assignment-1c/examples/.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
# Necessary files
!/earthtexture.ppm

View file

@ -0,0 +1,17 @@
eye 0 0 0
viewdir 0 0 -1
updir 0 1 0
hfov 60
imsize 512 256
bkgcolor 0.1 0.1 0.1
light -2 1 0 1 1 1 1
mtlcolor 1 0 1 1 1 1 0.1 0.4 0.4 20
v -2 1 -5
v -1 -1 -4
v 1 -1 -4
v 2 1 -6
f 1 2 3
f 1 3 4

View file

@ -0,0 +1,21 @@
eye 0 0 0
viewdir 0 0 -1
updir 0 1 0
hfov 60
imsize 512 256
bkgcolor 0.1 0.1 0.1
light -2 1 0 1 1 1 1
mtlcolor 1 0 1 1 1 1 0.1 0.4 0.4 20
v -2 1 -5
v -1 -1 -4
v 1 -1 -4
v 2 1 -6
vn -2 1 1
vn -1 -1 1
vn 2 1 1
f 1//1 2//2 3//3
f 1 3 4

View file

@ -0,0 +1,10 @@
eye 2 -6 1
viewdir -1 3 -0.5
updir 0 0 1
hfov 50
imsize 512 512
bkgcolor 0.5 0.7 0.9
light 0 1 -1 0 1 1 1
mtlcolor 0 1 0 1 1 1 0.2 0.8 0.1 20
texture earthtexture.ppm
sphere 0 0 0 2

File diff suppressed because one or more lines are too long

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.9 KiB

View file

@ -0,0 +1,14 @@
eye 0 0 0
viewdir 0 0 -1
updir 0 1 0
hfov 60
imsize 512 256
bkgcolor 0.1 0.1 0.1
light -2 1 0 1 1 1 1
mtlcolor 0 1 0 1 1 1 0.2 0.6 0.2 20
v 0 1 -4
v -1 -1 -4
v 1 -1 -4
v 2 1 -6
f 1 2 3
f 1 3 4

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -0,0 +1,18 @@
eye 0 0 0
viewdir 0 0 -1
updir 0 1 0
hfov 60
imsize 512 256
bkgcolor 0.1 0.1 0.1
light -2 1 0 1 1 1 1
mtlcolor 0 0 1 1 1 1 0.2 0.6 0.2 20
v -1 1 -4
v -1 -1 -4
v 1 -1 -4
v 1 1 -4
vn -1 1 1
vn -1 -1 1
vn 1 -1 1
vn 1 1 1
f 1//1 2//2 3//3
f 1//1 3//3 4//4

Binary file not shown.

After

Width:  |  Height:  |  Size: 105 KiB

View file

@ -0,0 +1,10 @@
eye 2 -6 1
viewdir -1 3 -0.5
updir 0 0 1
hfov 50
imsize 512 512
bkgcolor 0.5 0.7 0.9
light 0 1 -1 0 1 1 1
mtlcolor 0 1 0 1 1 1 0.2 0.8 0.1 20
texture earthtexture.ppm
sphere 0 0 0 2

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

View file

@ -0,0 +1,19 @@
eye 0 0 0
viewdir 0 0 -1
updir 0 1 0
hfov 60
imsize 512 256
bkgcolor 0.1 0.1 0.1
light -2 1 0 1 1 1 1
mtlcolor 0 0 1 1 1 1 0.2 0.6 0.2 20
texture umn.ppm
v -1 1 -4
v -1 -1 -4
v 1 -1 -4
v 1 1 -4
vt 0 0
vt 0 1
vt 1 1
vt 1 0
f 1/1 2/2 3/3
f 1/1 3/3 4/4

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

View file

@ -0,0 +1,25 @@
eye 0 0 0
viewdir 0 0 -1
updir 0 1 0
hfov 60
imsize 512 256
bkgcolor 0.1 0.1 0.1
light -2 1 0 1 1 1 1
mtlcolor 0 0 1 1 1 1 0.2 0.6 0.2 20
texture umn.ppm
v -1 1 -4
v -1 -1 -4
v 1 -1 -4
v 1 1 -4
vn -1 1 1
vn -1 -1 1
vn 1 -1 1
vn 1 1 1
vt 0 0
vt 0 1
vt 1 1
vt 1 0
f 1/1/1 2/2/2 3/3/3
f 1/1/1 3/3/3 4/4/4

Binary file not shown.

After

Width:  |  Height:  |  Size: 640 KiB

View file

@ -0,0 +1,56 @@
eye 12.5 5 2.5
viewdir -2 -0.5 2
updir 0 1 0
hfov 60
imsize 900 600
bkgcolor 0.5 0.7 0.9
light 1 -1 1 0 1 1 1
mtlcolor 0 1 0 1 1 1 0.2 0.8 0 20
v 10 0 5
v -10 0 5
v -10 0 25
v 10 0 25
v 5 0 12.5
v -5 0 12.5
v -5 5 12.5
v 5 5 12.5
v 5 0 17.5
v -5 0 17.5
v -5 5 17.5
v 5 5 17.5
v 5 7.5 15
v -5 7.5 15
v 5 4.5 12
v -5 4.5 12
v 5 4.5 18
v -5 4.5 18
vt 0 0
vt 0 1
vt 1 1
vt 1 0
vt 2 0
vt 2 1
vt 0.5 0
vt 4 0
vt 4 1
texture grass.ppm
f 1/2 2/3 3/4
f 1/2 3/4 4/1
texture wood.ppm
f 5/2 6/6 7/5
f 5/2 7/5 8/1
f 9/2 11/5 10/6
f 9/2 12/1 11/5
f 6/2 10/3 11/4
f 6/2 11/4 7/1
f 5/3 12/1 9/2
f 5/3 8/4 12/1
f 7/2 11/3 14/7
f 8/3 13/7 12/2
texture redwood.ppm
f 13/1 15/2 16/9
f 13/1 16/9 14/8
f 13/8 14/1 18/2
f 13/8 18/2 17/9
texture soccerball.ppm
sphere -2.5 0.5 9 0.5

Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

View file

@ -0,0 +1,20 @@
eye 0 0 0
viewdir 0 0 -1
updir 0 1 0
hfov 60
imsize 512 256
bkgcolor 0.1 0.1 0.1
light -2 1 0 1 1 1 1
mtlcolor 1 1 1 1 1 1 0.2 0.6 0.2 20 1 0
bump normalmap.ppm
sphere -1.6 0 -4 0.5
v -1 1 -4
v -1 -1 -4
v 1 -1 -4
v 1 1 -4
vt 0 0
vt 0 1
vt 1 1
vt 1 0
f 1/1 2/2 3/3
f 1/1 3/3 4/4

Some files were not shown because too many files have changed in this diff Show more