Produce images
This commit is contained in:
parent
4af669099b
commit
15b1191c69
10 changed files with 252 additions and 52 deletions
1
assignment-1/.gitignore
vendored
1
assignment-1/.gitignore
vendored
|
@ -1 +1,2 @@
|
||||||
/target
|
/target
|
||||||
|
*.ppm
|
||||||
|
|
26
assignment-1/Cargo.lock
generated
26
assignment-1/Cargo.lock
generated
|
@ -14,7 +14,9 @@ version = "0.1.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"anyhow",
|
"anyhow",
|
||||||
"clap",
|
"clap",
|
||||||
|
"itertools",
|
||||||
"num",
|
"num",
|
||||||
|
"ordered-float",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
|
@ -72,6 +74,12 @@ dependencies = [
|
||||||
"os_str_bytes",
|
"os_str_bytes",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "either"
|
||||||
|
version = "1.8.1"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "7fcaabb2fef8c910e7f4c7ce9f67a1283a1715879a7c230ca9d6d1ae31f16d91"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "errno"
|
name = "errno"
|
||||||
version = "0.2.8"
|
version = "0.2.8"
|
||||||
|
@ -130,6 +138,15 @@ 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"
|
||||||
|
@ -227,6 +244,15 @@ version = "1.17.0"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "6f61fba1741ea2b3d6a1e3178721804bb716a68a6aeba1149b5d52e3d464ea66"
|
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]]
|
[[package]]
|
||||||
name = "os_str_bytes"
|
name = "os_str_bytes"
|
||||||
version = "6.4.1"
|
version = "6.4.1"
|
||||||
|
|
|
@ -8,4 +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"
|
||||||
|
|
35
assignment-1/src/image.rs
Normal file
35
assignment-1/src/image.rs
Normal file
|
@ -0,0 +1,35 @@
|
||||||
|
use std::io::{Result, Write};
|
||||||
|
|
||||||
|
/// A 24-bit pixel represented by a red, green, and blue value.
|
||||||
|
#[derive(Clone, Copy, Default, Debug)]
|
||||||
|
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(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -54,11 +54,17 @@ pub fn parse_input_file(path: impl AsRef<Path>) -> Result<Scene> {
|
||||||
|
|
||||||
"hfov" => scene.hfov = parts[0],
|
"hfov" => scene.hfov = parts[0],
|
||||||
"bkgcolor" => scene.bkg_color = read_vec3()?,
|
"bkgcolor" => scene.bkg_color = read_vec3()?,
|
||||||
"mtlcolor" => material_color = Some(read_vec3()?),
|
|
||||||
|
"mtlcolor" => {
|
||||||
|
let idx = scene.material_colors.len();
|
||||||
|
material_color = Some(idx);
|
||||||
|
scene.material_colors.push(read_vec3()?);
|
||||||
|
},
|
||||||
|
|
||||||
"sphere" => scene.objects.push(Object::Sphere(Sphere {
|
"sphere" => scene.objects.push(Object::Sphere(Sphere {
|
||||||
center: read_vec3()?,
|
center: read_vec3()?,
|
||||||
radius: parts[3],
|
radius: parts[3],
|
||||||
|
material: material_color.unwrap(),
|
||||||
})),
|
})),
|
||||||
_ => bail!("Unknown keyword {keyword}"),
|
_ => bail!("Unknown keyword {keyword}"),
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,21 +1,30 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
extern crate anyhow;
|
extern crate anyhow;
|
||||||
|
|
||||||
|
mod image;
|
||||||
mod input_file;
|
mod input_file;
|
||||||
mod ray;
|
mod ray;
|
||||||
mod scene_data;
|
mod scene_data;
|
||||||
mod vec3;
|
mod vec3;
|
||||||
mod view;
|
mod view;
|
||||||
|
|
||||||
|
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 crate::image::{Image, Pixel};
|
||||||
use crate::input_file::parse_input_file;
|
use crate::input_file::parse_input_file;
|
||||||
|
use crate::ray::Ray;
|
||||||
|
use crate::scene_data::Object;
|
||||||
use crate::vec3::Vec3;
|
use crate::vec3::Vec3;
|
||||||
use crate::view::Rect;
|
use crate::view::Rect;
|
||||||
|
|
||||||
|
const ARBITRARY_D: f64 = 2.0;
|
||||||
|
|
||||||
/// Simple raycaster.
|
/// Simple raycaster.
|
||||||
#[derive(Parser)]
|
#[derive(Parser)]
|
||||||
#[clap(author, version, about, long_about = None)]
|
#[clap(author, version, about, long_about = None)]
|
||||||
|
@ -26,14 +35,20 @@ struct Opt {
|
||||||
///
|
///
|
||||||
/// imsize [width] [height]
|
/// imsize [width] [height]
|
||||||
///
|
///
|
||||||
/// Where `imsize' is a keyword, and `width' and `height' are integer values
|
/// Where `imsize' is a keyword, and `width' and `height' are integer values denoting the desired
|
||||||
/// denoting the desired size of the image to be generated.
|
/// size of the image to be generated.
|
||||||
#[clap()]
|
#[clap()]
|
||||||
input_path: PathBuf,
|
input_path: PathBuf,
|
||||||
|
|
||||||
|
#[clap()]
|
||||||
|
output_path: Option<PathBuf>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let opt = Opt::parse();
|
let opt = Opt::parse();
|
||||||
|
let out_file = opt
|
||||||
|
.output_path
|
||||||
|
.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:?}");
|
println!("Scene: {scene:?}");
|
||||||
|
@ -42,20 +57,88 @@ fn main() -> Result<()> {
|
||||||
let u = Vec3::cross(scene.view_dir, scene.up_dir).unit();
|
let u = Vec3::cross(scene.view_dir, scene.up_dir).unit();
|
||||||
let v = Vec3::cross(u, scene.view_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
|
// Compute viewing window corners
|
||||||
// TODO: See slide 101
|
// TODO: See slide 101
|
||||||
// Also need to reverse calculation for d based on hfov
|
|
||||||
let n = scene.view_dir.unit();
|
let n = scene.view_dir.unit();
|
||||||
let d = 1.0;
|
#[rustfmt::skip] // Otherwise this line wraps over
|
||||||
let view_window = Rect {
|
let view_window = Rect {
|
||||||
upper_left: scene.eye_pos + n * d, // + ...
|
upper_left: scene.eye_pos + n * ARBITRARY_D - u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
||||||
upper_right: scene.eye_pos + n * d,
|
upper_right: scene.eye_pos + n * ARBITRARY_D + u * (viewing_width / 2.0) + v * (viewing_height / 2.0),
|
||||||
lower_left: scene.eye_pos + n * d,
|
lower_left: scene.eye_pos + n * ARBITRARY_D - u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
||||||
lower_right: scene.eye_pos + n * d,
|
lower_right: scene.eye_pos + n * ARBITRARY_D + u * (viewing_width / 2.0) - v * (viewing_height / 2.0),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Translate image pixels to real-world 3d coords
|
||||||
|
let pixel_translation = {
|
||||||
|
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 = view_window.upper_left + pixel_base_x * px as f64;
|
||||||
|
let y_component = view_window.upper_left + pixel_base_y * py as f64;
|
||||||
|
x_component + y_component
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// Loop through every single pixel of the output file
|
// Loop through every single pixel of the output file
|
||||||
for (px, py) in (0..scene.image_width).zip(0..scene.image_height) {}
|
let mut pixels =
|
||||||
|
vec![Pixel::default(); scene.image_width * scene.image_width];
|
||||||
|
for (px, py) in (0..scene.image_width).cartesian_product(0..scene.image_height) {
|
||||||
|
let pixel_in_space = pixel_translation(px, py);
|
||||||
|
let ray = Ray::from_endpoints(scene.eye_pos, pixel_in_space);
|
||||||
|
|
||||||
|
let earliest_intersection = scene
|
||||||
|
.objects
|
||||||
|
.iter()
|
||||||
|
.filter_map(|object| {
|
||||||
|
let sphere = match object {
|
||||||
|
Object::Sphere(v) => v,
|
||||||
|
_ => return None, // TODO: Handle other object types for intersection as well
|
||||||
|
};
|
||||||
|
|
||||||
|
ray.intersects_at(sphere).map(|t| (t, sphere))
|
||||||
|
})
|
||||||
|
.min_by_key(|(t, _)| NotNan::new(*t).unwrap());
|
||||||
|
|
||||||
|
let pixel_color = match earliest_intersection {
|
||||||
|
Some((_, sphere)) => scene.material_colors[sphere.material],
|
||||||
|
// There was no intersection, so this should default to the background color
|
||||||
|
None => scene.bkg_color,
|
||||||
|
};
|
||||||
|
|
||||||
|
// println!("({px}, {py}): {intersection:?}\t{ray:?} {sphere:?}");
|
||||||
|
|
||||||
|
pixels[py * scene.image_height + px] = pixel_color.to_pixel();
|
||||||
|
}
|
||||||
|
|
||||||
|
let image = Image {
|
||||||
|
width: scene.image_width,
|
||||||
|
height: scene.image_height,
|
||||||
|
data: pixels,
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
let file = File::create(&out_file)?;
|
||||||
|
image.write(file)?;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,52 +5,63 @@ use crate::vec3::Vec3;
|
||||||
///
|
///
|
||||||
/// That means at any time t: f64, the point represented by origin + direction * time occurs on the
|
/// That means at any time t: f64, the point represented by origin + direction * time occurs on the
|
||||||
/// ray.
|
/// ray.
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
origin: Vec3,
|
origin: Vec3,
|
||||||
direction: Vec3,
|
direction: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Ray {
|
impl Ray {
|
||||||
|
/// Construct a ray from endpoints
|
||||||
|
pub fn from_endpoints(start: Vec3, end: Vec3) -> Self {
|
||||||
|
let delta = (end - start).unit();
|
||||||
|
Ray {
|
||||||
|
origin: start,
|
||||||
|
direction: delta,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Evaluate the ray at a certain point in time, yielding a point
|
/// Evaluate the ray at a certain point in time, yielding a point
|
||||||
pub fn eval(&self, time: f64) -> Vec3 {
|
pub fn eval(&self, time: f64) -> Vec3 {
|
||||||
self.origin + self.direction * time
|
self.origin + self.direction * time
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/// Given a ray and a sphere, returns the first time at which this ray intersects the sphere.
|
/// Given a sphere, returns the first time at which this ray intersects the sphere.
|
||||||
///
|
///
|
||||||
/// If there is no intersection point, returns None.
|
/// If there is no intersection point, returns None.
|
||||||
pub fn ray_intersection_time(ray: &Ray, sphere: &Sphere) -> Option<f64> {
|
pub fn intersects_at(&self, sphere: &Sphere) -> Option<f64> {
|
||||||
let a =
|
let a = self.direction.x.powi(2)
|
||||||
ray.direction.x.powi(2) + ray.direction.y.powi(2) + ray.direction.z.powi(2);
|
+ self.direction.y.powi(2)
|
||||||
let b = 2.0
|
+ self.direction.z.powi(2);
|
||||||
* (ray.direction.x * (ray.origin.x - sphere.center.x)
|
let b = 2.0
|
||||||
+ ray.direction.y * (ray.origin.y - sphere.center.y)
|
* (self.direction.x * (self.origin.x - sphere.center.x)
|
||||||
+ ray.direction.z * (ray.origin.z - sphere.center.z));
|
+ self.direction.y * (self.origin.y - sphere.center.y)
|
||||||
let c = (ray.origin.x - sphere.center.x).powi(2)
|
+ self.direction.z * (self.origin.z - sphere.center.z));
|
||||||
+ (ray.origin.y - sphere.center.y).powi(2)
|
let c = (self.origin.x - sphere.center.x).powi(2)
|
||||||
+ (ray.origin.z - sphere.center.z).powi(2)
|
+ (self.origin.y - sphere.center.y).powi(2)
|
||||||
- sphere.radius.powi(2);
|
+ (self.origin.z - sphere.center.z).powi(2)
|
||||||
let discriminant = b * b - 4.0 * a * c;
|
- sphere.radius.powi(2);
|
||||||
|
let discriminant = b * b - 4.0 * a * c;
|
||||||
|
|
||||||
match discriminant {
|
match discriminant {
|
||||||
// Discriminant < 0, means the equation has no solutions.
|
// Discriminant < 0, means the equation has no solutions.
|
||||||
d if d < 0.0 => return None,
|
d if d < 0.0 => return None,
|
||||||
|
|
||||||
// Discriminant == 0
|
// Discriminant == 0
|
||||||
d if d == 0.0 => {
|
d if d == 0.0 => {
|
||||||
return Some(-b / (2.0 * a));
|
return 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);
|
||||||
|
|
||||||
|
return Some(solution_1.min(solution_2));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Probably hit some NaN or Infinity value due to faulty inputs...
|
||||||
|
_ => unreachable!("Invalid determinant value: {discriminant}"),
|
||||||
}
|
}
|
||||||
|
|
||||||
d if d > 0.0 => {
|
|
||||||
let solution_1 = (-b + discriminant.sqrt()) / (2.0 * a);
|
|
||||||
let solution_2 = (-b - discriminant.sqrt()) / (2.0 * a);
|
|
||||||
|
|
||||||
return Some(solution_1.min(solution_2));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Probably hit some NaN or Infinity value due to faulty inputs...
|
|
||||||
_ => unreachable!("Invalid determinant value: {discriminant}"),
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -59,7 +70,7 @@ mod tests {
|
||||||
use crate::scene_data::Sphere;
|
use crate::scene_data::Sphere;
|
||||||
use crate::vec3::Vec3;
|
use crate::vec3::Vec3;
|
||||||
|
|
||||||
use super::{ray_intersection_time, Ray};
|
use super::Ray;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn practice_problem_slide_154() {
|
fn practice_problem_slide_154() {
|
||||||
|
@ -72,7 +83,7 @@ mod tests {
|
||||||
radius: 4.0,
|
radius: 4.0,
|
||||||
};
|
};
|
||||||
|
|
||||||
let point = ray_intersection_time(&ray, &sphere).map(|t| ray.eval(t));
|
let point = ray.intersects_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)));
|
||||||
|
@ -90,6 +101,6 @@ mod tests {
|
||||||
};
|
};
|
||||||
|
|
||||||
// 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_intersection_time(&ray, &sphere), None);
|
assert_eq!(ray.intersects_at(&sphere), None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,9 @@ use crate::vec3::Vec3;
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
pub center: Vec3,
|
pub center: Vec3,
|
||||||
pub radius: f64,
|
pub radius: f64,
|
||||||
|
|
||||||
|
/// Index into the scene's material color list
|
||||||
|
pub material: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
|
@ -32,8 +35,6 @@ pub struct Scene {
|
||||||
/// Background color
|
/// Background color
|
||||||
pub bkg_color: Vec3,
|
pub bkg_color: Vec3,
|
||||||
|
|
||||||
/// Material color
|
pub material_colors: Vec<Vec3>,
|
||||||
pub mtl_color: Vec3,
|
|
||||||
|
|
||||||
pub objects: Vec<Object>,
|
pub objects: Vec<Object>,
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use std::ops::{Add, Mul};
|
use std::ops::{Add, Div, Mul, Sub};
|
||||||
|
|
||||||
use num::Float;
|
use num::Float;
|
||||||
|
|
||||||
|
use crate::image::Pixel;
|
||||||
|
|
||||||
#[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,
|
||||||
|
@ -37,6 +39,16 @@ impl<T: Float> Vec3<T> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Vec3<f64> {
|
||||||
|
/// Convert into an RGB color
|
||||||
|
pub fn to_pixel(&self) -> Pixel {
|
||||||
|
let r = (self.x * 256.0) as u8;
|
||||||
|
let g = (self.y * 256.0) as u8;
|
||||||
|
let b = (self.z * 256.0) as u8;
|
||||||
|
Pixel(r, g, b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Vector addition
|
/// Vector addition
|
||||||
impl<T> Add<Vec3<T>> for Vec3<T>
|
impl<T> Add<Vec3<T>> for Vec3<T>
|
||||||
where
|
where
|
||||||
|
@ -49,6 +61,18 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Vector subtraction
|
||||||
|
impl<T> Sub<Vec3<T>> for Vec3<T>
|
||||||
|
where
|
||||||
|
T: Sub<T>,
|
||||||
|
{
|
||||||
|
type Output = Vec3<T::Output>;
|
||||||
|
|
||||||
|
fn sub(self, rhs: Vec3<T>) -> Self::Output {
|
||||||
|
Vec3::new(self.x - rhs.x, self.y - rhs.y, self.z - rhs.z)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Scalar multiplication
|
/// Scalar multiplication
|
||||||
impl<T, U> Mul<U> for Vec3<T>
|
impl<T, U> Mul<U> for Vec3<T>
|
||||||
where
|
where
|
||||||
|
@ -62,6 +86,19 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Scalar division
|
||||||
|
impl<T, U> Div<U> for Vec3<T>
|
||||||
|
where
|
||||||
|
T: Div<U, Output = U>,
|
||||||
|
U: Copy,
|
||||||
|
{
|
||||||
|
type Output = Vec3<U>;
|
||||||
|
|
||||||
|
fn div(self, rhs: U) -> Self::Output {
|
||||||
|
Vec3::new(self.x / rhs, self.y / rhs, self.z / rhs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::Vec3;
|
use super::Vec3;
|
||||||
|
|
|
@ -8,6 +8,4 @@ pub struct Rect {
|
||||||
pub lower_right: Vec3,
|
pub lower_right: Vec3,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_viewing_rect() {
|
pub fn compute_viewing_rect() {}
|
||||||
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue