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 = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
"itertools",
|
||||
"num",
|
||||
"ordered-float",
|
||||
"rayon",
|
||||
|
@ -188,15 +187,6 @@ dependencies = [
|
|||
"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 = "libc"
|
||||
version = "0.2.139"
|
||||
|
|
|
@ -8,7 +8,6 @@ edition = "2021"
|
|||
[dependencies]
|
||||
anyhow = "1.0.68"
|
||||
clap = { version = "4.1.4", features = ["derive"] }
|
||||
itertools = "0.10.5"
|
||||
num = { version = "0.4.0", features = ["serde"] }
|
||||
ordered-float = "3.4.0"
|
||||
rayon = "1.6.1"
|
||||
|
|
|
@ -6,14 +6,12 @@ mod input_file;
|
|||
mod ray;
|
||||
mod scene_data;
|
||||
mod vec3;
|
||||
mod view;
|
||||
|
||||
use std::fs::File;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use anyhow::Result;
|
||||
use clap::Parser;
|
||||
use itertools::Itertools;
|
||||
use ordered_float::NotNan;
|
||||
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
||||
|
||||
|
@ -21,28 +19,21 @@ use crate::image::Image;
|
|||
use crate::input_file::parse_input_file;
|
||||
use crate::ray::Ray;
|
||||
use crate::scene_data::Object;
|
||||
use crate::vec3::Vec3;
|
||||
use crate::view::Rect;
|
||||
|
||||
/// Viewing distance
|
||||
const ARBITRARY_D: f64 = 2.0;
|
||||
const ARBITRARY_D: f64 = 1.0;
|
||||
|
||||
/// Simple raycaster.
|
||||
#[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,
|
||||
|
||||
#[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>,
|
||||
}
|
||||
|
||||
|
@ -53,38 +44,9 @@ fn main() -> Result<()> {
|
|||
.unwrap_or_else(|| opt.input_path.with_extension("ppm"));
|
||||
|
||||
let scene = parse_input_file(&opt.input_path)?;
|
||||
println!("Scene: {scene:?}");
|
||||
|
||||
// Compute viewing directions
|
||||
let u = Vec3::cross(scene.view_dir, scene.up_dir).unit();
|
||||
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:?}");
|
||||
// Compute the viewing window
|
||||
let view_window = scene.compute_viewing_window();
|
||||
|
||||
// Translate image pixels to real-world 3d coords
|
||||
let translate_pixel = {
|
||||
|
@ -93,7 +55,6 @@ fn main() -> Result<()> {
|
|||
|
||||
let dy = view_window.lower_left - view_window.upper_left;
|
||||
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| {
|
||||
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)
|
||||
.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
|
||||
let pixels = pixels_iter
|
||||
|
@ -122,9 +85,19 @@ fn main() -> Result<()> {
|
|||
* 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 {
|
||||
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)]
|
||||
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)]
|
||||
pub struct Scene {
|
||||
pub eye_pos: Vec3,
|
||||
|
@ -38,3 +46,40 @@ pub struct Scene {
|
|||
pub material_colors: Vec<Color>,
|
||||
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 crate::image::Color;
|
||||
|
||||
#[derive(Copy, Clone, Default, Debug, PartialEq, Eq)]
|
||||
pub struct Vec3<T = f64> {
|
||||
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