Add parallel projection support
This commit is contained in:
parent
b4feb95743
commit
7885b58947
10 changed files with 166 additions and 51 deletions
1
assignment-1/.gitignore
vendored
1
assignment-1/.gitignore
vendored
|
@ -1,5 +1,6 @@
|
||||||
/target
|
/target
|
||||||
/assignment-1
|
/assignment-1
|
||||||
|
/examples/*.png
|
||||||
*.ppm
|
*.ppm
|
||||||
*.zip
|
*.zip
|
||||||
*.pdf
|
*.pdf
|
||||||
|
|
|
@ -3,12 +3,17 @@
|
||||||
DOCKER := docker
|
DOCKER := docker
|
||||||
ZIP := zip
|
ZIP := zip
|
||||||
PANDOC := pandoc
|
PANDOC := pandoc
|
||||||
|
CONVERT := convert
|
||||||
|
|
||||||
HANDIN := hw1a.michael.zhang.zip
|
HANDIN := hw1a.michael.zhang.zip
|
||||||
BINARY := assignment-1
|
BINARY := ./assignment-1
|
||||||
WRITEUP := writeup.pdf
|
WRITEUP := writeup.pdf
|
||||||
SOURCES := $(shell find -name "*.rs")
|
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)
|
all: $(HANDIN)
|
||||||
|
|
||||||
$(BINARY): $(SOURCES)
|
$(BINARY): $(SOURCES)
|
||||||
|
@ -22,8 +27,14 @@ $(BINARY): $(SOURCES)
|
||||||
cargo build --release
|
cargo build --release
|
||||||
mv target/release/assignment-1 $@
|
mv target/release/assignment-1 $@
|
||||||
|
|
||||||
$(HANDIN): $(BINARY) $(WRITEUP) Cargo.toml Cargo.lock README.md
|
$(HANDIN): $(BINARY) $(WRITEUP) Cargo.toml Cargo.lock README.md $(EXAMPLES_PNG) $(EXAMPLES_PPM)
|
||||||
$(ZIP) -r $@ src $^
|
$(ZIP) -r $@ src examples $^
|
||||||
|
|
||||||
|
examples/%.ppm: examples/%.txt
|
||||||
|
cargo run -- -o $@ $<
|
||||||
|
|
||||||
|
examples/%.png: examples/%.ppm
|
||||||
|
convert $< $@
|
||||||
|
|
||||||
writeup.pdf: writeup.md
|
writeup.pdf: writeup.md
|
||||||
$(PANDOC) -o $@ $<
|
$(PANDOC) -o $@ $<
|
||||||
|
|
|
@ -1,11 +1,18 @@
|
||||||
# Raycaster
|
# Raycaster
|
||||||
|
|
||||||
|
## Bundle contents
|
||||||
|
|
||||||
Writeup is located at `/writeup.pdf`.
|
Writeup is located at `/writeup.pdf`.
|
||||||
|
|
||||||
The binary can be found at `/assignment-1`. Run `./assignment-1 --help` to see
|
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
|
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
|
should have an environment similar to CSELabs. If there is trouble running the
|
||||||
binary, try building form source:
|
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
|
## Building from source
|
||||||
|
|
||||||
|
|
21
assignment-1/examples/objects.txt
Normal file
21
assignment-1/examples/objects.txt
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
imsize 640 480
|
||||||
|
eye 0 0 3
|
||||||
|
viewdir 0 0 -1
|
||||||
|
hfov 120
|
||||||
|
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 2 1 -2 1 -2 1 1 2
|
|
@ -4,7 +4,7 @@ use anyhow::Result;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
image::Color,
|
image::Color,
|
||||||
scene_data::{Object, Scene, Sphere},
|
scene_data::{Cylinder, Object, ObjectKind, Scene, Sphere},
|
||||||
vec3::Vec3,
|
vec3::Vec3,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -35,6 +35,10 @@ pub fn parse_input_file(path: impl AsRef<Path>) -> Result<Scene> {
|
||||||
scene.image_width = width;
|
scene.image_width = width;
|
||||||
scene.image_height = height;
|
scene.image_height = height;
|
||||||
}
|
}
|
||||||
|
} else if keyword == "projection" {
|
||||||
|
if let Some("parallel") = parts.next() {
|
||||||
|
scene.parallel_projection = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
// Do float parsing instead
|
// Do float parsing instead
|
||||||
else {
|
else {
|
||||||
|
@ -42,11 +46,11 @@ pub fn parse_input_file(path: impl AsRef<Path>) -> Result<Scene> {
|
||||||
.map(|s| s.parse::<f64>().map_err(|e| e.into()))
|
.map(|s| s.parse::<f64>().map_err(|e| e.into()))
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
let read_vec3 = || {
|
let read_vec3 = |start: usize| {
|
||||||
if parts.len() < 3 {
|
if parts.len() < 3 {
|
||||||
bail!("Vec3 requires 3 components.");
|
bail!("Vec3 requires 3 components.");
|
||||||
}
|
}
|
||||||
Ok(Vec3::new(parts[0], parts[1], parts[2]))
|
Ok(Vec3::new(parts[start], parts[start + 1], parts[start + 2]))
|
||||||
};
|
};
|
||||||
|
|
||||||
let read_color = || {
|
let read_color = || {
|
||||||
|
@ -57,9 +61,9 @@ pub fn parse_input_file(path: impl AsRef<Path>) -> Result<Scene> {
|
||||||
};
|
};
|
||||||
|
|
||||||
match keyword {
|
match keyword {
|
||||||
"eye" => scene.eye_pos = read_vec3()?,
|
"eye" => scene.eye_pos = read_vec3(0)?,
|
||||||
"viewdir" => scene.view_dir = read_vec3()?,
|
"viewdir" => scene.view_dir = read_vec3(0)?,
|
||||||
"updir" => scene.up_dir = read_vec3()?,
|
"updir" => scene.up_dir = read_vec3(0)?,
|
||||||
|
|
||||||
"hfov" => scene.hfov = parts[0],
|
"hfov" => scene.hfov = parts[0],
|
||||||
"bkgcolor" => scene.bkg_color = read_color()?,
|
"bkgcolor" => scene.bkg_color = read_color()?,
|
||||||
|
@ -70,11 +74,30 @@ pub fn parse_input_file(path: impl AsRef<Path>) -> Result<Scene> {
|
||||||
scene.material_colors.push(read_color()?);
|
scene.material_colors.push(read_color()?);
|
||||||
}
|
}
|
||||||
|
|
||||||
"sphere" => scene.objects.push(Object::Sphere(Sphere {
|
"sphere" => scene.objects.push(Object {
|
||||||
center: read_vec3()?,
|
kind: ObjectKind::Sphere(Sphere {
|
||||||
radius: parts[3],
|
center: read_vec3(0)?,
|
||||||
material: material_color.unwrap(),
|
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}"),
|
_ => bail!("Unknown keyword {keyword}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,14 +14,11 @@ use anyhow::Result;
|
||||||
use clap::Parser;
|
use clap::Parser;
|
||||||
use ordered_float::NotNan;
|
use ordered_float::NotNan;
|
||||||
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
||||||
|
use scene_data::ObjectKind;
|
||||||
|
|
||||||
use crate::image::Image;
|
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;
|
|
||||||
|
|
||||||
/// Viewing distance
|
|
||||||
const ARBITRARY_D: f64 = 1.0;
|
|
||||||
|
|
||||||
/// Simple raycaster.
|
/// Simple raycaster.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
|
@ -35,6 +32,14 @@ struct Opt {
|
||||||
/// with an extension of .ppm)
|
/// with an extension of .ppm)
|
||||||
#[clap(short = 'o', long = "output")]
|
#[clap(short = 'o', long = "output")]
|
||||||
output_path: Option<PathBuf>,
|
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<()> {
|
fn main() -> Result<()> {
|
||||||
|
@ -43,10 +48,15 @@ fn main() -> Result<()> {
|
||||||
.output_path
|
.output_path
|
||||||
.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 mut scene = parse_input_file(&opt.input_path)?;
|
||||||
|
let distance = opt.distance;
|
||||||
|
|
||||||
|
if opt.force_parallel {
|
||||||
|
scene.parallel_projection = true;
|
||||||
|
}
|
||||||
|
|
||||||
// Compute the viewing window
|
// Compute the viewing window
|
||||||
let view_window = scene.compute_viewing_window();
|
let view_window = scene.compute_viewing_window(distance);
|
||||||
|
|
||||||
// Translate image pixels to real-world 3d coords
|
// Translate image pixels to real-world 3d coords
|
||||||
let translate_pixel = {
|
let translate_pixel = {
|
||||||
|
@ -79,34 +89,50 @@ fn main() -> Result<()> {
|
||||||
let pixels = pixels_iter
|
let pixels = pixels_iter
|
||||||
.map(|(px, py)| {
|
.map(|(px, py)| {
|
||||||
let pixel_in_space = translate_pixel(px, py);
|
let pixel_in_space = translate_pixel(px, py);
|
||||||
let ray = Ray::from_endpoints(scene.eye_pos, pixel_in_space);
|
|
||||||
|
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.unit();
|
||||||
|
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
|
let earliest_intersection = scene
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.filter_map(|object| {
|
.filter_map(|object| {
|
||||||
let sphere = match object {
|
use ObjectKind::*;
|
||||||
Object::Sphere(v) => v,
|
let intersection_point_opt = match &object.kind {
|
||||||
_ => return None, /* TODO: Handle other object types for
|
Sphere(sphere) => ray.intersects_sphere_at(&sphere),
|
||||||
* intersection as well */
|
Cylinder(cylinder) => ray.intersects_cylinder_at(&cylinder),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return both the t and the sphere, because we want to sort on the t
|
intersection_point_opt.and_then(|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,
|
// Unfortunately, IEEE floats in Rust don't have total ordering,
|
||||||
// because NaNs violate ordering properties. The way to remedy this
|
// because NaNs violate ordering properties. The way to remedy this
|
||||||
// is to ensure we don't have NaNs by wrapping it into this type,
|
// is to ensure we don't have NaNs by wrapping it into this type,
|
||||||
// which then implements total ordering
|
// which then implements total ordering
|
||||||
let t = NotNan::new(t);
|
let t = NotNan::new(t);
|
||||||
t.ok().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
|
||||||
|
t.ok().map(|t| (t, object))
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
// Sort the list of intersection times by the lowest one.
|
// Sort the list of intersection times by the lowest one.
|
||||||
.min_by_key(|(t, _)| *t);
|
.min_by_key(|(t, _)| *t);
|
||||||
|
|
||||||
let pixel_color = match earliest_intersection {
|
let pixel_color = match earliest_intersection {
|
||||||
Some((_, sphere)) => scene.material_colors[sphere.material],
|
// Take the object's material color
|
||||||
|
Some((_, object)) => scene.material_colors[object.material],
|
||||||
|
|
||||||
// There was no intersection, so this should default to the scene's
|
// There was no intersection, so this should default to the scene's
|
||||||
// background color
|
// background color
|
||||||
None => scene.bkg_color,
|
None => scene.bkg_color,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::scene_data::Sphere;
|
use crate::scene_data::{Cylinder, Sphere};
|
||||||
use crate::vec3::Vec3;
|
use crate::vec3::Vec3;
|
||||||
|
|
||||||
/// A normalized parametric Ray of the form (origin + direction * time)
|
/// A normalized parametric Ray of the form (origin + direction * time)
|
||||||
|
@ -30,7 +30,7 @@ impl Ray {
|
||||||
/// sphere.
|
/// sphere.
|
||||||
///
|
///
|
||||||
/// If there is no intersection point, returns None.
|
/// If there is no intersection point, returns None.
|
||||||
pub fn intersects_at(&self, sphere: &Sphere) -> Option<f64> {
|
pub fn intersects_sphere_at(&self, sphere: &Sphere) -> Option<f64> {
|
||||||
let a = self.direction.x.powi(2)
|
let a = self.direction.x.powi(2)
|
||||||
+ self.direction.y.powi(2)
|
+ self.direction.y.powi(2)
|
||||||
+ self.direction.z.powi(2);
|
+ self.direction.z.powi(2);
|
||||||
|
@ -64,6 +64,15 @@ impl Ray {
|
||||||
_ => unreachable!("Invalid determinant value: {discriminant}"),
|
_ => unreachable!("Invalid determinant value: {discriminant}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// 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_cylinder_at(&self, cylinder: &Cylinder) -> Option<f64> {
|
||||||
|
// TODO: Implement
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
@ -82,10 +91,9 @@ mod tests {
|
||||||
let sphere = Sphere {
|
let sphere = Sphere {
|
||||||
center: Vec3::new(0.0, 0.0, -10.0),
|
center: Vec3::new(0.0, 0.0, -10.0),
|
||||||
radius: 4.0,
|
radius: 4.0,
|
||||||
material: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
let point = ray.intersects_at(&sphere).map(|t| ray.eval(t));
|
let point = ray.intersects_sphere_at(&sphere).map(|t| ray.eval(t));
|
||||||
|
|
||||||
// the intersection point in this case is (0, 0, -6)
|
// the intersection point in this case is (0, 0, -6)
|
||||||
assert_eq!(point, Some(Vec3::new(0.0, 0.0, -6.0)));
|
assert_eq!(point, Some(Vec3::new(0.0, 0.0, -6.0)));
|
||||||
|
@ -100,10 +108,9 @@ mod tests {
|
||||||
let sphere = Sphere {
|
let sphere = Sphere {
|
||||||
center: Vec3::new(0.0, 0.0, -10.0),
|
center: Vec3::new(0.0, 0.0, -10.0),
|
||||||
radius: 4.0,
|
radius: 4.0,
|
||||||
material: 0,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
// oops! In this case, the ray does not intersect the sphere.
|
// oops! In this case, the ray does not intersect the sphere.
|
||||||
assert_eq!(ray.intersects_at(&sphere), None);
|
assert_eq!(ray.intersects_sphere_at(&sphere), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,23 +1,31 @@
|
||||||
use crate::{image::Color, vec3::Vec3, ARBITRARY_D};
|
use crate::{image::Color, vec3::Vec3};
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
pub center: Vec3,
|
pub center: Vec3,
|
||||||
pub radius: f64,
|
pub radius: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Cylinder {
|
||||||
|
pub center: Vec3,
|
||||||
|
pub direction: Vec3,
|
||||||
|
pub radius: f64,
|
||||||
|
pub length: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Object {
|
||||||
|
pub kind: ObjectKind,
|
||||||
|
|
||||||
/// Index into the scene's material color list
|
/// Index into the scene's material color list
|
||||||
pub material: usize,
|
pub material: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub enum Object {
|
pub enum ObjectKind {
|
||||||
Sphere(Sphere),
|
Sphere(Sphere),
|
||||||
Cylinder {
|
Cylinder(Cylinder),
|
||||||
center: Vec3,
|
|
||||||
direction: Vec3,
|
|
||||||
radius: f64,
|
|
||||||
length: f64,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -36,6 +44,7 @@ pub struct Scene {
|
||||||
|
|
||||||
/// Horizontal field of view (in degrees)
|
/// Horizontal field of view (in degrees)
|
||||||
pub hfov: f64,
|
pub hfov: f64,
|
||||||
|
pub parallel_projection: bool,
|
||||||
|
|
||||||
pub image_width: usize,
|
pub image_width: usize,
|
||||||
pub image_height: usize,
|
pub image_height: usize,
|
||||||
|
@ -49,7 +58,7 @@ pub struct Scene {
|
||||||
|
|
||||||
impl Scene {
|
impl Scene {
|
||||||
/// Determine the boundaries of the viewing window in world coordinates
|
/// Determine the boundaries of the viewing window in world coordinates
|
||||||
pub fn compute_viewing_window(&self) -> Rect {
|
pub fn compute_viewing_window(&self, distance: f64) -> Rect {
|
||||||
// Compute viewing directions
|
// Compute viewing directions
|
||||||
let u = Vec3::cross(self.view_dir, self.up_dir).unit();
|
let u = Vec3::cross(self.view_dir, self.up_dir).unit();
|
||||||
let v = Vec3::cross(u, self.view_dir).unit();
|
let v = Vec3::cross(u, self.view_dir).unit();
|
||||||
|
@ -64,7 +73,7 @@ impl Scene {
|
||||||
let w_over_2d = half_hfov.tan();
|
let w_over_2d = half_hfov.tan();
|
||||||
|
|
||||||
// To find the viewing width we must multiply by 2d now
|
// To find the viewing width we must multiply by 2d now
|
||||||
w_over_2d * 2.0 * ARBITRARY_D
|
w_over_2d * 2.0 * distance
|
||||||
};
|
};
|
||||||
|
|
||||||
let aspect_ratio = self.image_width as f64 / self.image_height as f64;
|
let aspect_ratio = self.image_width as f64 / self.image_height as f64;
|
||||||
|
@ -74,10 +83,10 @@ impl Scene {
|
||||||
let n = self.view_dir.unit();
|
let n = self.view_dir.unit();
|
||||||
#[rustfmt::skip] // Otherwise this line wraps over
|
#[rustfmt::skip] // Otherwise this line wraps over
|
||||||
let view_window = Rect {
|
let view_window = Rect {
|
||||||
upper_left: self.eye_pos + n * ARBITRARY_D - u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
upper_left: self.eye_pos + n * distance - 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),
|
upper_right: self.eye_pos + n * distance + 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_left: self.eye_pos + n * distance - 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),
|
lower_right: self.eye_pos + n * distance + u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
||||||
};
|
};
|
||||||
|
|
||||||
view_window
|
view_window
|
||||||
|
|
|
@ -71,3 +71,12 @@ location for any pixel.
|
||||||
|
|
||||||
(Technically really we would want the middle of the pixel, so just add
|
(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)
|
$\frac{\Delta x + \Delta y}{2}$ to the point to get that)
|
||||||
|
|
||||||
|
### 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)$.
|
||||||
|
|
||||||
|
Then it's a matter of determining if the $x$ and $y$ coordinates fall into the
|
||||||
|
space constrained by the equation $x^2 + y^2 = r^2$ and if $z \le L$.
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
zip
|
zip
|
||||||
unzip
|
unzip
|
||||||
texlive.combined.scheme-full
|
texlive.combined.scheme-full
|
||||||
|
imagemagick
|
||||||
]) ++ (with toolchain; [
|
]) ++ (with toolchain; [
|
||||||
cargo
|
cargo
|
||||||
rustc
|
rustc
|
||||||
|
|
Loading…
Reference in a new issue