Fix radians bug, polish and refactor

This commit is contained in:
Michael Zhang 2023-02-01 02:23:45 -06:00
parent 00000150bc
commit 00000160d0
6 changed files with 67 additions and 73 deletions

View file

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

View file

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

View file

@ -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],

View file

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

View file

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

View file

@ -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() {}