Fix radians bug, polish and refactor
This commit is contained in:
parent
00000150bc
commit
00000160d0
6 changed files with 67 additions and 73 deletions
10
assignment-1/Cargo.lock
generated
10
assignment-1/Cargo.lock
generated
|
@ -14,7 +14,6 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
"itertools",
|
|
||||||
"num",
|
"num",
|
||||||
"ordered-float",
|
"ordered-float",
|
||||||
"rayon",
|
"rayon",
|
||||||
|
@ -188,15 +187,6 @@ dependencies = [
|
||||||
"windows-sys",
|
"windows-sys",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "itertools"
|
|
||||||
version = "0.10.5"
|
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "b0fd2260e829bddf4cb6ea802289de2f86d6a7a690192fbe91b3f46e0f2c8473"
|
|
||||||
dependencies = [
|
|
||||||
"either",
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "libc"
|
name = "libc"
|
||||||
version = "0.2.139"
|
version = "0.2.139"
|
||||||
|
|
|
@ -8,7 +8,6 @@ edition = "2021"
|
||||||
[dependencies]
|
[dependencies]
|
||||||
anyhow = "1.0.68"
|
anyhow = "1.0.68"
|
||||||
clap = { version = "4.1.4", features = ["derive"] }
|
clap = { version = "4.1.4", features = ["derive"] }
|
||||||
itertools = "0.10.5"
|
|
||||||
num = { version = "0.4.0", features = ["serde"] }
|
num = { version = "0.4.0", features = ["serde"] }
|
||||||
ordered-float = "3.4.0"
|
ordered-float = "3.4.0"
|
||||||
rayon = "1.6.1"
|
rayon = "1.6.1"
|
||||||
|
|
|
@ -6,14 +6,12 @@ mod input_file;
|
||||||
mod ray;
|
mod ray;
|
||||||
mod scene_data;
|
mod scene_data;
|
||||||
mod vec3;
|
mod vec3;
|
||||||
mod view;
|
|
||||||
|
|
||||||
use std::fs::File;
|
use std::fs::File;
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use itertools::Itertools;
|
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
||||||
|
|
||||||
|
@ -21,28 +19,21 @@ use crate::image::Image;
|
||||||
use crate::input_file::parse_input_file;
|
use crate::input_file::parse_input_file;
|
||||||
use crate::ray::Ray;
|
use crate::ray::Ray;
|
||||||
use crate::scene_data::Object;
|
use crate::scene_data::Object;
|
||||||
use crate::vec3::Vec3;
|
|
||||||
use crate::view::Rect;
|
|
||||||
|
|
||||||
/// Viewing distance
|
/// Viewing distance
|
||||||
const ARBITRARY_D: f64 = 2.0;
|
const ARBITRARY_D: f64 = 1.0;
|
||||||
|
|
||||||
/// Simple raycaster.
|
/// Simple raycaster.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
struct Opt {
|
struct Opt {
|
||||||
/// Path to the input file to use.
|
/// 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()]
|
#[clap()]
|
||||||
input_path: PathBuf,
|
input_path: PathBuf,
|
||||||
|
|
||||||
#[clap()]
|
/// 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>,
|
output_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -53,38 +44,9 @@ fn main() -> Result<()> {
|
||||||
.unwrap_or_else(|| opt.input_path.with_extension("ppm"));
|
.unwrap_or_else(|| opt.input_path.with_extension("ppm"));
|
||||||
|
|
||||||
let scene = parse_input_file(&opt.input_path)?;
|
let scene = parse_input_file(&opt.input_path)?;
|
||||||
println!("Scene: {scene:?}");
|
|
||||||
|
|
||||||
// Compute viewing directions
|
// Compute the viewing window
|
||||||
let u = Vec3::cross(scene.view_dir, scene.up_dir).unit();
|
let view_window = scene.compute_viewing_window();
|
||||||
let v = Vec3::cross(u, scene.view_dir).unit();
|
|
||||||
|
|
||||||
// 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 = scene.hfov / 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 * ARBITRARY_D
|
|
||||||
};
|
|
||||||
let aspect_ratio = scene.image_width as f64 / scene.image_height as f64;
|
|
||||||
let viewing_height = viewing_width / aspect_ratio;
|
|
||||||
|
|
||||||
// Compute viewing window corners
|
|
||||||
let n = scene.view_dir.unit();
|
|
||||||
#[rustfmt::skip] // Otherwise this line wraps over
|
|
||||||
let view_window = Rect {
|
|
||||||
upper_left: scene.eye_pos + n * ARBITRARY_D - u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
|
||||||
upper_right: scene.eye_pos + n * ARBITRARY_D + u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
|
||||||
lower_left: scene.eye_pos + n * ARBITRARY_D - u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
|
||||||
lower_right: scene.eye_pos + n * ARBITRARY_D + u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
|
||||||
};
|
|
||||||
|
|
||||||
println!("Coords: {view_window:?}");
|
|
||||||
|
|
||||||
// Translate image pixels to real-world 3d coords
|
// Translate image pixels to real-world 3d coords
|
||||||
let translate_pixel = {
|
let translate_pixel = {
|
||||||
|
@ -93,7 +55,6 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
let dy = view_window.lower_left - view_window.upper_left;
|
let dy = view_window.lower_left - view_window.upper_left;
|
||||||
let pixel_base_y = dy / scene.image_height as f64;
|
let pixel_base_y = dy / scene.image_height as f64;
|
||||||
println!("Base components in 3D space: {pixel_base_x} / {pixel_base_y}");
|
|
||||||
|
|
||||||
move |px: usize, py: usize| {
|
move |px: usize, py: usize| {
|
||||||
let x_component = pixel_base_x * px as f64;
|
let x_component = pixel_base_x * px as f64;
|
||||||
|
@ -102,9 +63,11 @@ fn main() -> Result<()> {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Generate a parallel iterator for pixels
|
||||||
|
// The iterator preserves order and uses row-major order
|
||||||
let pixels_iter = (0..scene.image_height)
|
let pixels_iter = (0..scene.image_height)
|
||||||
.into_par_iter()
|
.into_par_iter()
|
||||||
.flat_map(|x| (0..scene.image_width).into_par_iter().map(move |y| (y, x)));
|
.flat_map(|y| (0..scene.image_width).into_par_iter().map(move |x| (x, y)));
|
||||||
|
|
||||||
// Loop through every single pixel of the output file
|
// Loop through every single pixel of the output file
|
||||||
let pixels = pixels_iter
|
let pixels = pixels_iter
|
||||||
|
@ -122,9 +85,19 @@ fn main() -> Result<()> {
|
||||||
* intersection as well */
|
* intersection as well */
|
||||||
};
|
};
|
||||||
|
|
||||||
ray.intersects_at(sphere).map(|t| (t, sphere))
|
// Return both the t and the sphere, because we want to sort on the t
|
||||||
|
// but later retrieve attributes from the sphere
|
||||||
|
ray.intersects_at(sphere).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);
|
||||||
|
t.ok().map(|t| (t, sphere))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
.min_by_key(|(t, _)| NotNan::new(*t).unwrap());
|
// Sort the list of intersection times by the lowest one.
|
||||||
|
.min_by_key(|(t, _)| *t);
|
||||||
|
|
||||||
let pixel_color = match earliest_intersection {
|
let pixel_color = match earliest_intersection {
|
||||||
Some((_, sphere)) => scene.material_colors[sphere.material],
|
Some((_, sphere)) => scene.material_colors[sphere.material],
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::{image::Color, vec3::Vec3};
|
use crate::{image::Color, vec3::Vec3, ARBITRARY_D};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
|
@ -20,6 +20,14 @@ pub enum Object {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Rect {
|
||||||
|
pub upper_left: Vec3,
|
||||||
|
pub upper_right: Vec3,
|
||||||
|
pub lower_left: Vec3,
|
||||||
|
pub lower_right: Vec3,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
pub struct Scene {
|
pub struct Scene {
|
||||||
pub eye_pos: Vec3,
|
pub eye_pos: Vec3,
|
||||||
|
@ -38,3 +46,40 @@ pub struct Scene {
|
||||||
pub material_colors: Vec<Color>,
|
pub material_colors: Vec<Color>,
|
||||||
pub objects: Vec<Object>,
|
pub objects: Vec<Object>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Scene {
|
||||||
|
/// Determine the boundaries of the viewing window in world coordinates
|
||||||
|
pub fn compute_viewing_window(&self) -> Rect {
|
||||||
|
// Compute viewing directions
|
||||||
|
let u = Vec3::cross(self.view_dir, self.up_dir).unit();
|
||||||
|
let v = Vec3::cross(u, self.view_dir).unit();
|
||||||
|
|
||||||
|
// 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 * ARBITRARY_D
|
||||||
|
};
|
||||||
|
|
||||||
|
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.unit();
|
||||||
|
#[rustfmt::skip] // Otherwise this line wraps over
|
||||||
|
let view_window = Rect {
|
||||||
|
upper_left: self.eye_pos + n * ARBITRARY_D - u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
||||||
|
upper_right: self.eye_pos + n * ARBITRARY_D + u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
||||||
|
lower_left: self.eye_pos + n * ARBITRARY_D - u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
||||||
|
lower_right: self.eye_pos + n * ARBITRARY_D + u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
view_window
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ use std::ops::{Add, Div, Mul, Sub};
|
||||||
|
|
||||||
use num::Float;
|
use num::Float;
|
||||||
|
|
||||||
use crate::image::Color;
|
|
||||||
|
|
||||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
|
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
|
||||||
pub struct Vec3<T = f64> {
|
pub struct Vec3<T = f64> {
|
||||||
pub x: T,
|
pub x: T,
|
||||||
|
|
|
@ -1,11 +0,0 @@
|
||||||
use crate::vec3::Vec3;
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Rect {
|
|
||||||
pub upper_left: Vec3,
|
|
||||||
pub upper_right: Vec3,
|
|
||||||
pub lower_left: Vec3,
|
|
||||||
pub lower_right: Vec3,
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn compute_viewing_rect() {}
|
|
Loading…
Reference in a new issue