Compare commits
No commits in common. "839642b88656dcdf1eadb34ec4724d82c49272fe" and "dbf545107024893436778362684ca69d9a8a3a97" have entirely different histories.
839642b886
...
dbf5451070
@ -1,7 +1,5 @@
|
||||
#!/usr/bin/env bash
|
||||
set -e
|
||||
SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
|
||||
|
||||
pushd () {
|
||||
command pushd "$@" > /dev/null
|
||||
}
|
||||
@ -20,14 +18,9 @@ RT=rtchallenge
|
||||
AFFECTED="$(git diff-index --cached --name-only HEAD)"
|
||||
#echo "AFFECTED $AFFECTED"
|
||||
RT_AFFECTED=$(echo "${AFFECTED:?}" | grep ${RT:?} || true)
|
||||
NO_FEATURES_TARGET_DIR="${SCRIPT_DIR:?}/../../rtchallenge/target/no-default-features"
|
||||
if [ ! -z "$RT_AFFECTED" ]; then
|
||||
echo "Files for the rt challenge were touched, running tests"
|
||||
cd ${RT:?} && \
|
||||
cargo build --examples && \
|
||||
cargo test && \
|
||||
cargo build --target-dir=${NO_FEATURES_TARGET_DIR:?} --no-default-features --examples && \
|
||||
cargo test --target-dir=${NO_FEATURES_TARGET_DIR:?} --no-default-features
|
||||
cd ${RT:?} && cargo build --examples && cargo test
|
||||
fi
|
||||
# Uncomment to debug presubmit.
|
||||
#exit 1
|
||||
|
||||
66
rtchallenge/Cargo.lock
generated
66
rtchallenge/Cargo.lock
generated
@ -1,7 +1,5 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "addr2line"
|
||||
version = "0.15.2"
|
||||
@ -308,17 +306,6 @@ dependencies = [
|
||||
"backtrace",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7fcd999463524c52659517fe2cea98493cfe485d10565e7b0fb07dbba7ad2753"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "gimli"
|
||||
version = "0.24.0"
|
||||
@ -521,12 +508,6 @@ dependencies = [
|
||||
"miniz_oxide 0.3.7",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ac74c624d6b2d21f425f752262f42188365d7b8ff1aff74c82e45136510a4857"
|
||||
|
||||
[[package]]
|
||||
name = "proc-macro-error"
|
||||
version = "1.0.4"
|
||||
@ -569,46 +550,6 @@ dependencies = [
|
||||
"proc-macro2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.4"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2e7573632e6454cf6b99d7aac4ccca54be06da05aca2ef7423d22d27d4d4bcd8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d51e9f596de227fda2ea6c84607f5558e196eeaf43c986b724ba4fb8fdf497e7"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.5.1"
|
||||
@ -665,7 +606,6 @@ dependencies = [
|
||||
"enum-utils",
|
||||
"num_cpus",
|
||||
"png",
|
||||
"rand",
|
||||
"rayon",
|
||||
"serde",
|
||||
"serde_json",
|
||||
@ -888,12 +828,6 @@ dependencies = [
|
||||
"winapi-util",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.10.2+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "fd6fbd9a79829dd1ad0cc20627bf1ed606756a7f77edff7b66b7064f9cb327c6"
|
||||
|
||||
[[package]]
|
||||
name = "wasm-bindgen"
|
||||
version = "0.2.74"
|
||||
|
||||
@ -7,9 +7,7 @@ edition = "2018"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[features]
|
||||
default = [ "float-as-double" ]
|
||||
disable-inverse-cache = []
|
||||
float-as-double = []
|
||||
disable_inverse_cache = []
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.41"
|
||||
@ -18,7 +16,6 @@ criterion = "0.3.4"
|
||||
enum-utils = "0.1.2"
|
||||
num_cpus = "1.13.0"
|
||||
png = "0.16.8"
|
||||
rand = "0.8.4"
|
||||
rayon = "1.5.1"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
serde_json = "1.0.64"
|
||||
|
||||
@ -3,7 +3,6 @@ use anyhow::{bail, Result};
|
||||
use rtchallenge::{
|
||||
canvas::Canvas,
|
||||
tuples::{Color, Tuple},
|
||||
Float,
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
@ -48,8 +47,8 @@ fn main() -> Result<()> {
|
||||
let bg = Color::new(0.2, 0.2, 0.2);
|
||||
let mut c = Canvas::new(w, h, bg);
|
||||
let mut i = 0;
|
||||
let w = w as Float;
|
||||
let h = h as Float;
|
||||
let w = w as f32;
|
||||
let h = h as f32;
|
||||
while p.position.y > 0. {
|
||||
p = tick(&e, &p);
|
||||
println!("tick {}: proj {:?}", i, p);
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
use std::f32::consts::PI;
|
||||
|
||||
use anyhow::Result;
|
||||
|
||||
use rtchallenge::{
|
||||
canvas::Canvas,
|
||||
float::consts::PI,
|
||||
matrices::Matrix4x4,
|
||||
tuples::{Color, Tuple},
|
||||
Float,
|
||||
};
|
||||
|
||||
fn draw_dot(c: &mut Canvas, x: usize, y: usize) {
|
||||
@ -30,13 +30,13 @@ fn main() -> Result<()> {
|
||||
let rot_hour = Matrix4x4::rotation_z(-PI / 6.);
|
||||
|
||||
let mut p = t * p;
|
||||
let w = w as Float;
|
||||
let h = h as Float;
|
||||
let w = w as f32;
|
||||
let h = h as f32;
|
||||
// The 'world' exists between -0.5 - 0.5 in X-Y plane.
|
||||
// To convert to screen space, we translate by 0.5, scale to canvas size,
|
||||
// and invert the Y-axis.
|
||||
let world_to_screen =
|
||||
Matrix4x4::scaling(w as Float, -h as Float, 1.0) * Matrix4x4::translation(0.5, -0.5, 0.);
|
||||
Matrix4x4::scaling(w as f32, -h as f32, 1.0) * Matrix4x4::translation(0.5, -0.5, 0.);
|
||||
for _ in 0..12 {
|
||||
let canvas_pixel = world_to_screen * p;
|
||||
draw_dot(&mut c, canvas_pixel.x as usize, canvas_pixel.y as usize);
|
||||
|
||||
@ -6,7 +6,6 @@ use rtchallenge::{
|
||||
rays::Ray,
|
||||
spheres::{intersect, Sphere},
|
||||
tuples::{Color, Tuple},
|
||||
Float,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@ -17,7 +16,7 @@ fn main() -> Result<()> {
|
||||
let ray_origin = Tuple::point(0., 0., -5.);
|
||||
let wall_z = 10.;
|
||||
let wall_size = 7.;
|
||||
let pixel_size = wall_size / w as Float;
|
||||
let pixel_size = wall_size / w as f32;
|
||||
let half = wall_size / 2.;
|
||||
let color = Color::new(1., 0., 0.);
|
||||
let mut shape = Sphere::default();
|
||||
@ -26,9 +25,9 @@ fn main() -> Result<()> {
|
||||
);
|
||||
|
||||
for y in 0..h {
|
||||
let world_y = half - pixel_size * y as Float;
|
||||
let world_y = half - pixel_size * y as f32;
|
||||
for x in 0..w {
|
||||
let world_x = -half + pixel_size * x as Float;
|
||||
let world_x = -half + pixel_size * x as f32;
|
||||
let position = Tuple::point(world_x, world_y, wall_z);
|
||||
let r = Ray::new(ray_origin, (position - ray_origin).normalize());
|
||||
let xs = intersect(&shape, &r);
|
||||
|
||||
@ -7,7 +7,7 @@ use rtchallenge::{
|
||||
rays::Ray,
|
||||
spheres::{intersect, Sphere},
|
||||
tuples::{Color, Tuple},
|
||||
Float, WHITE,
|
||||
WHITE,
|
||||
};
|
||||
|
||||
fn main() -> Result<()> {
|
||||
@ -18,7 +18,7 @@ fn main() -> Result<()> {
|
||||
let ray_origin = Tuple::point(0., 0., -5.);
|
||||
let wall_z = 10.;
|
||||
let wall_size = 7.;
|
||||
let pixel_size = wall_size / w as Float;
|
||||
let pixel_size = wall_size / w as f32;
|
||||
let half = wall_size / 2.;
|
||||
let mut shape = Sphere::default();
|
||||
shape.material = Material {
|
||||
@ -31,11 +31,11 @@ fn main() -> Result<()> {
|
||||
let light_position = Tuple::point(-10., 10., -10.);
|
||||
let light_color = WHITE;
|
||||
let light = PointLight::new(light_position, light_color);
|
||||
let in_shadow = false;
|
||||
|
||||
for y in 0..h {
|
||||
let world_y = half - pixel_size * y as Float;
|
||||
let world_y = half - pixel_size * y as f32;
|
||||
for x in 0..w {
|
||||
let world_x = -half + pixel_size * x as Float;
|
||||
let world_x = -half + pixel_size * x as f32;
|
||||
let position = Tuple::point(world_x, world_y, wall_z);
|
||||
let direction = (position - ray_origin).normalize();
|
||||
let r = Ray::new(ray_origin, direction);
|
||||
@ -44,7 +44,7 @@ fn main() -> Result<()> {
|
||||
let point = r.position(hit.t);
|
||||
let normal = hit.object.normal_at(point);
|
||||
let eye = -r.direction;
|
||||
let color = lighting(&hit.object.material, &light, point, eye, normal, in_shadow);
|
||||
let color = lighting(&hit.object.material, &light, point, eye, normal);
|
||||
c.set(x, y, color);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,11 +1,10 @@
|
||||
use std::time::Instant;
|
||||
use std::{f32::consts::PI, time::Instant};
|
||||
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use rtchallenge::{
|
||||
camera::{Camera, RenderStrategy},
|
||||
float::consts::PI,
|
||||
lights::PointLight,
|
||||
materials::Material,
|
||||
matrices::Matrix4x4,
|
||||
|
||||
@ -1,113 +0,0 @@
|
||||
use std::time::Instant;
|
||||
|
||||
use anyhow::Result;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use rtchallenge::{
|
||||
camera::{Camera, RenderStrategy},
|
||||
float::consts::PI,
|
||||
lights::PointLight,
|
||||
materials::Material,
|
||||
matrices::Matrix4x4,
|
||||
spheres::Sphere,
|
||||
transformations::view_transform,
|
||||
tuples::{Color, Tuple},
|
||||
world::World,
|
||||
WHITE,
|
||||
};
|
||||
|
||||
/// End of chapter 8 challenge.
|
||||
#[derive(StructOpt, Debug)]
|
||||
#[structopt(name = "eoc8")]
|
||||
struct Opt {
|
||||
#[structopt(long, default_value = "rayon")]
|
||||
render_strategy: RenderStrategy,
|
||||
/// Number of samples per pixel. 0 renders from the center of the pixel, 1 or more samples N
|
||||
/// times randomly across the pixel.
|
||||
#[structopt(short, long, default_value = "0")]
|
||||
samples: usize,
|
||||
}
|
||||
|
||||
fn main() -> Result<()> {
|
||||
let start = Instant::now();
|
||||
let opt = Opt::from_args();
|
||||
let width = 2560;
|
||||
let height = 1440;
|
||||
let light_position = Tuple::point(-10., 10., -10.);
|
||||
let light_color = WHITE;
|
||||
let light = PointLight::new(light_position, light_color);
|
||||
let mut camera = Camera::new(width, height, PI / 4.);
|
||||
let from = Tuple::point(0., 1.5, -5.);
|
||||
let to = Tuple::point(0., 1., 0.);
|
||||
let up = Tuple::point(0., 1., 0.);
|
||||
camera.set_transform(view_transform(from, to, up));
|
||||
camera.render_strategy = opt.render_strategy;
|
||||
camera.samples_per_pixel = opt.samples;
|
||||
|
||||
let mut floor = Sphere::default();
|
||||
floor.set_transform(Matrix4x4::scaling(10., 0.01, 10.));
|
||||
floor.material = Material {
|
||||
color: Color::new(1., 0.9, 0.9),
|
||||
specular: 0.,
|
||||
..Material::default()
|
||||
};
|
||||
|
||||
let mut left_wall = Sphere::default();
|
||||
left_wall.set_transform(
|
||||
Matrix4x4::translation(0., 0., 5.)
|
||||
* Matrix4x4::rotation_y(-PI / 4.)
|
||||
* Matrix4x4::rotation_x(PI / 2.)
|
||||
* Matrix4x4::scaling(10., 0.01, 10.),
|
||||
);
|
||||
left_wall.material = floor.material.clone();
|
||||
|
||||
let mut right_wall = Sphere::default();
|
||||
right_wall.set_transform(
|
||||
Matrix4x4::translation(0., 0., 5.)
|
||||
* Matrix4x4::rotation_y(PI / 4.)
|
||||
* Matrix4x4::rotation_x(PI / 2.)
|
||||
* Matrix4x4::scaling(10., 0.00001, 10.),
|
||||
);
|
||||
right_wall.material = floor.material.clone();
|
||||
|
||||
let mut middle = Sphere::default();
|
||||
middle.set_transform(Matrix4x4::translation(-0.5, 1., 0.5));
|
||||
middle.material = Material {
|
||||
color: Color::new(0.1, 1., 0.5),
|
||||
diffuse: 0.7,
|
||||
specular: 0.3,
|
||||
..Material::default()
|
||||
};
|
||||
|
||||
let mut right = Sphere::default();
|
||||
right.set_transform(Matrix4x4::translation(1.5, 0.5, -0.5) * Matrix4x4::scaling(0.5, 0.5, 0.5));
|
||||
right.material = Material {
|
||||
color: Color::new(0.5, 1., 0.1),
|
||||
diffuse: 0.7,
|
||||
specular: 0.3,
|
||||
..Material::default()
|
||||
};
|
||||
|
||||
let mut left = Sphere::default();
|
||||
left.set_transform(
|
||||
Matrix4x4::translation(-1.5, 0.33, -0.75) * Matrix4x4::scaling(0.33, 0.33, 0.33),
|
||||
);
|
||||
left.material = Material {
|
||||
color: Color::new(1., 0.8, 0.1),
|
||||
diffuse: 0.7,
|
||||
specular: 0.3,
|
||||
..Material::default()
|
||||
};
|
||||
|
||||
let mut world = World::default();
|
||||
world.light = Some(light);
|
||||
world.objects = vec![floor, left_wall, right_wall, middle, right, left];
|
||||
|
||||
let image = camera.render(&world);
|
||||
|
||||
let path = "/tmp/eoc8.png";
|
||||
println!("saving output to {}", path);
|
||||
image.write_to_file(path)?;
|
||||
println!("Render time {:.3} seconds", start.elapsed().as_secs_f32());
|
||||
Ok(())
|
||||
}
|
||||
@ -7,19 +7,11 @@ use std::{
|
||||
thread,
|
||||
};
|
||||
|
||||
use rand::Rng;
|
||||
use rayon::iter::{IntoParallelIterator, ParallelIterator};
|
||||
use serde::Deserialize;
|
||||
use structopt::StructOpt;
|
||||
|
||||
use crate::{
|
||||
canvas::Canvas,
|
||||
matrices::Matrix4x4,
|
||||
rays::Ray,
|
||||
tuples::{Color, Tuple},
|
||||
world::World,
|
||||
Float, BLACK,
|
||||
};
|
||||
use crate::{canvas::Canvas, matrices::Matrix4x4, rays::Ray, tuples::Tuple, world::World, BLACK};
|
||||
|
||||
#[derive(Copy, Clone, StructOpt, Debug, Deserialize)]
|
||||
#[serde(rename_all = "kebab-case")]
|
||||
@ -39,15 +31,13 @@ impl FromStr for RenderStrategy {
|
||||
pub struct Camera {
|
||||
hsize: usize,
|
||||
vsize: usize,
|
||||
field_of_view: Float,
|
||||
field_of_view: f32,
|
||||
transform: Matrix4x4,
|
||||
inverse_transform: Matrix4x4,
|
||||
pixel_size: Float,
|
||||
half_width: Float,
|
||||
half_height: Float,
|
||||
pixel_size: f32,
|
||||
half_width: f32,
|
||||
half_height: f32,
|
||||
pub render_strategy: RenderStrategy,
|
||||
/// 0 renders from the center of the pixel, 1 or higher is random sampling of the pixel.
|
||||
pub samples_per_pixel: usize,
|
||||
}
|
||||
|
||||
enum Request {
|
||||
@ -64,7 +54,9 @@ impl Camera {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{camera::Camera, float::consts::PI, matrices::Matrix4x4, EPSILON};
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// use rtchallenge::{camera::Camera, matrices::Matrix4x4};
|
||||
///
|
||||
/// let hsize = 160;
|
||||
/// let vsize = 120;
|
||||
@ -76,21 +68,21 @@ impl Camera {
|
||||
///
|
||||
/// // Pixel size for a horizontal canvas.
|
||||
/// let c = Camera::new(200, 150, PI / 2.);
|
||||
/// assert!((c.pixel_size() - 0.010).abs() < EPSILON);
|
||||
/// assert_eq!(c.pixel_size(), 0.01);
|
||||
///
|
||||
/// // Pixel size for a horizontal canvas.
|
||||
/// let c = Camera::new(150, 200, PI / 2.);
|
||||
/// assert!((c.pixel_size() - 0.010).abs() < EPSILON);
|
||||
/// assert_eq!(c.pixel_size(), 0.01);
|
||||
/// ```
|
||||
pub fn new(hsize: usize, vsize: usize, field_of_view: Float) -> Camera {
|
||||
pub fn new(hsize: usize, vsize: usize, field_of_view: f32) -> Camera {
|
||||
let half_view = (field_of_view / 2.).tan();
|
||||
let aspect = hsize as Float / vsize as Float;
|
||||
let aspect = hsize as f32 / vsize as f32;
|
||||
let (half_width, half_height) = if aspect >= 1. {
|
||||
(half_view, half_view / aspect)
|
||||
} else {
|
||||
(half_view * aspect, half_view)
|
||||
};
|
||||
let pixel_size = 2. * half_width / hsize as Float;
|
||||
let pixel_size = 2. * half_width / hsize as f32;
|
||||
Camera {
|
||||
hsize,
|
||||
vsize,
|
||||
@ -100,8 +92,7 @@ impl Camera {
|
||||
pixel_size,
|
||||
half_height,
|
||||
half_width,
|
||||
render_strategy: RenderStrategy::Rayon,
|
||||
samples_per_pixel: 0,
|
||||
render_strategy: RenderStrategy::WorkerPool,
|
||||
}
|
||||
}
|
||||
pub fn hsize(&self) -> usize {
|
||||
@ -110,7 +101,7 @@ impl Camera {
|
||||
pub fn vsize(&self) -> usize {
|
||||
self.vsize
|
||||
}
|
||||
pub fn field_of_view(&self) -> Float {
|
||||
pub fn field_of_view(&self) -> f32 {
|
||||
self.field_of_view
|
||||
}
|
||||
pub fn transform(&self) -> Matrix4x4 {
|
||||
@ -120,43 +111,18 @@ impl Camera {
|
||||
self.transform = t;
|
||||
self.inverse_transform = t.inverse();
|
||||
}
|
||||
pub fn pixel_size(&self) -> Float {
|
||||
pub fn pixel_size(&self) -> f32 {
|
||||
self.pixel_size
|
||||
}
|
||||
pub fn supersample_rays_for_pixel(&self, px: usize, py: usize, samples: usize) -> Vec<Ray> {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
(0..samples)
|
||||
.map(|_| {
|
||||
// The offset from the edge of the canvas to the pixel's corner.
|
||||
let xoffset = (px as Float + rng.gen::<Float>()) * self.pixel_size;
|
||||
let yoffset = (py as Float + rng.gen::<Float>()) * self.pixel_size;
|
||||
|
||||
// The untransformed coordinates of the pixle in world space.
|
||||
// (Remember that the camera looks toward -z, so +x is to the left.)
|
||||
let world_x = self.half_width - xoffset;
|
||||
let world_y = self.half_height - yoffset;
|
||||
|
||||
// Using the camera matrix, transofmrm the canvas point and the origin,
|
||||
// and then compute the ray's direction vector.
|
||||
// (Remember that the canvas is at z>=-1).
|
||||
let pixel = self.inverse_transform * Tuple::point(world_x, world_y, -1.);
|
||||
let origin = self.inverse_transform * Tuple::point(0., 0., 0.);
|
||||
let direction = (pixel - origin).normalize();
|
||||
|
||||
Ray::new(origin, direction)
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Calculate ray that starts at the camera and passes through the (x,y)
|
||||
/// pixel on the canvas.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// camera::Camera, float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float,
|
||||
/// };
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// use rtchallenge::{camera::Camera, matrices::Matrix4x4, tuples::Tuple};
|
||||
///
|
||||
/// // Constructing a ray through the center of the canvas.
|
||||
/// let c = Camera::new(201, 101, PI / 2.);
|
||||
@ -177,14 +143,14 @@ impl Camera {
|
||||
/// assert_eq!(r.origin, Tuple::point(0., 2., -5.));
|
||||
/// assert_eq!(
|
||||
/// r.direction,
|
||||
/// Tuple::vector((2. as Float).sqrt() / 2., 0., -(2. as Float).sqrt() / 2.)
|
||||
/// Tuple::vector(2_f32.sqrt() / 2., 0., -2_f32.sqrt() / 2.)
|
||||
/// );
|
||||
/// ```
|
||||
#[cfg(not(feature = "disable-inverse-cache"))]
|
||||
#[cfg(not(feature = "disable_inverse_cache"))]
|
||||
pub fn ray_for_pixel(&self, px: usize, py: usize) -> Ray {
|
||||
// The offset from the edge of the canvas to the pixel's corner.
|
||||
let xoffset = (px as Float + 0.5) * self.pixel_size;
|
||||
let yoffset = (py as Float + 0.5) * self.pixel_size;
|
||||
let xoffset = (px as f32 + 0.5) * self.pixel_size;
|
||||
let yoffset = (py as f32 + 0.5) * self.pixel_size;
|
||||
|
||||
// The untransformed coordinates of the pixle in world space.
|
||||
// (Remember that the camera looks toward -z, so +x is to the left.)
|
||||
@ -200,11 +166,11 @@ impl Camera {
|
||||
|
||||
Ray::new(origin, direction)
|
||||
}
|
||||
#[cfg(feature = "disable-inverse-cache")]
|
||||
#[cfg(feature = "disable_inverse_cache")]
|
||||
pub fn ray_for_pixel(&self, px: usize, py: usize) -> Ray {
|
||||
// The offset from the edge of the canvas to the pixel's corner.
|
||||
let xoffset = (px as Float + 0.5) * self.pixel_size;
|
||||
let yoffset = (py as Float + 0.5) * self.pixel_size;
|
||||
let xoffset = (px as f32 + 0.5) * self.pixel_size;
|
||||
let yoffset = (py as f32 + 0.5) * self.pixel_size;
|
||||
|
||||
// The untransformed coordinates of the pixle in world space.
|
||||
// (Remember that the camera looks toward -z, so +x is to the left.)
|
||||
@ -224,9 +190,10 @@ impl Camera {
|
||||
/// Use camera to render an image of the given world.
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// use rtchallenge::{
|
||||
/// camera::Camera,
|
||||
/// float::consts::PI,
|
||||
/// transformations::view_transform,
|
||||
/// tuples::{Color, Tuple},
|
||||
/// world::World,
|
||||
@ -276,14 +243,15 @@ impl Camera {
|
||||
// Create a worker thread for each CPU core and pin the thread to the core.
|
||||
let mut handles = core_ids
|
||||
.into_iter()
|
||||
.map(|id| {
|
||||
.enumerate()
|
||||
.map(|(i, id)| {
|
||||
let w = Arc::clone(&world);
|
||||
let c = Arc::clone(&camera);
|
||||
let pixel_req_rx = pixel_req_rx.clone();
|
||||
let pixel_resp_tx = pixel_resp_tx.clone();
|
||||
thread::spawn(move || {
|
||||
core_affinity::set_for_current(id);
|
||||
render_worker_task(&c, &w, pixel_req_rx, &pixel_resp_tx);
|
||||
render_worker(i, &c, &w, pixel_req_rx, &pixel_resp_tx);
|
||||
})
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
@ -320,17 +288,18 @@ impl Camera {
|
||||
}
|
||||
|
||||
/// This renderer use rayon to split each row into a seperate thread. It
|
||||
/// seems to have more consistent performance than worker pool, equally fast
|
||||
/// as WP at WP's fastest. The downside is the flame graph looks a mess. A
|
||||
/// strength over `render_parallel_one_tread_per_core` is that it doesn't
|
||||
/// require `Camera` and `World` to be cloneable.
|
||||
/// seems to have really bad performance (only ~6x speedup over serial), and
|
||||
/// the flame graph looks a mess. A strength over
|
||||
/// `render_parallel_one_tread_per_core` is that it doesn't require `Camera`
|
||||
/// and `World` to be cloneable.
|
||||
fn render_parallel_rayon(&self, w: &World) -> Canvas {
|
||||
let image_mu = Mutex::new(Canvas::new(self.hsize, self.vsize, BLACK));
|
||||
|
||||
(0..self.vsize).into_par_iter().for_each(|y| {
|
||||
let mut row_image = Canvas::new(self.hsize, 1, BLACK);
|
||||
for x in 0..self.hsize {
|
||||
let color = self.sample(w, x, y);
|
||||
let ray = self.ray_for_pixel(x, y);
|
||||
let color = w.color_at(&ray);
|
||||
row_image.set(x, 0, color);
|
||||
}
|
||||
// TODO(wathiede): create a row based setter for memcpying the row as a whole.
|
||||
@ -349,28 +318,17 @@ impl Camera {
|
||||
let mut image = Canvas::new(self.hsize, self.vsize, BLACK);
|
||||
for y in 0..self.vsize {
|
||||
for x in 0..self.hsize {
|
||||
let color = self.sample(w, x, y);
|
||||
let ray = self.ray_for_pixel(x, y);
|
||||
let color = w.color_at(&ray);
|
||||
image.set(x, y, color);
|
||||
}
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
fn sample(&self, w: &World, x: usize, y: usize) -> Color {
|
||||
if self.samples_per_pixel > 0 {
|
||||
let color = self
|
||||
.supersample_rays_for_pixel(x, y, self.samples_per_pixel)
|
||||
.iter()
|
||||
.map(|ray| w.color_at(&ray))
|
||||
.fold(BLACK, |acc, c| acc + c);
|
||||
color / self.samples_per_pixel as Float
|
||||
} else {
|
||||
let ray = self.ray_for_pixel(x, y);
|
||||
w.color_at(&ray)
|
||||
}
|
||||
}
|
||||
}
|
||||
fn render_worker_task(
|
||||
|
||||
fn render_worker(
|
||||
tid: usize,
|
||||
c: &Camera,
|
||||
w: &World,
|
||||
input_chan: Arc<Mutex<Receiver<Request>>>,
|
||||
@ -379,18 +337,16 @@ fn render_worker_task(
|
||||
loop {
|
||||
let job = { input_chan.lock().unwrap().recv() };
|
||||
match job {
|
||||
Err(_) => {
|
||||
// From the docs:
|
||||
// "The recv operation can only fail if the sending half of a
|
||||
// channel (or sync_channel) is disconnected, implying that no
|
||||
// further messages will ever be received."
|
||||
Err(err) => {
|
||||
eprintln!("Shutting down render_worker {}: {}", tid, err);
|
||||
return;
|
||||
}
|
||||
Ok(req) => match req {
|
||||
Request::Line { width, y } => {
|
||||
let mut pixels = Canvas::new(width, 1, BLACK);
|
||||
for x in 0..width {
|
||||
let color = c.sample(w, x, y);
|
||||
let ray = c.ray_for_pixel(x, y);
|
||||
let color = w.color_at(&ray);
|
||||
pixels.set(x, 0, color);
|
||||
}
|
||||
output_chan
|
||||
|
||||
@ -4,12 +4,11 @@ use crate::{
|
||||
rays::Ray,
|
||||
spheres::Sphere,
|
||||
tuples::{dot, Tuple},
|
||||
Float, EPSILON,
|
||||
};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub struct Intersection<'i> {
|
||||
pub t: Float,
|
||||
pub t: f32,
|
||||
pub object: &'i Sphere,
|
||||
}
|
||||
|
||||
@ -26,7 +25,7 @@ impl<'i> Intersection<'i> {
|
||||
/// assert_eq!(i.t, 3.5);
|
||||
/// assert_eq!(i.object, &s);
|
||||
/// ```
|
||||
pub fn new(t: Float, object: &Sphere) -> Intersection {
|
||||
pub fn new(t: f32, object: &Sphere) -> Intersection {
|
||||
Intersection { t, object }
|
||||
}
|
||||
}
|
||||
@ -135,12 +134,10 @@ impl<'i> Index<usize> for Intersections<'i> {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PrecomputedData<'i> {
|
||||
pub t: Float,
|
||||
pub t: f32,
|
||||
pub object: &'i Sphere,
|
||||
pub point: Tuple,
|
||||
pub over_point: Tuple,
|
||||
pub eyev: Tuple,
|
||||
pub normalv: Tuple,
|
||||
pub inside: bool,
|
||||
@ -153,10 +150,8 @@ pub struct PrecomputedData<'i> {
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{prepare_computations, Intersection, Intersections},
|
||||
/// rays::Ray,
|
||||
/// matrices::Matrix4x4,
|
||||
/// spheres::{intersect, Sphere},
|
||||
/// tuples::Tuple,
|
||||
/// EPSILON
|
||||
/// };
|
||||
///
|
||||
/// // Precomputing the state of an intersection.
|
||||
@ -187,15 +182,6 @@ pub struct PrecomputedData<'i> {
|
||||
/// assert_eq!(comps.inside, true);
|
||||
//// // Normal would have been (0, 0, 1), but is inverted when inside.
|
||||
/// assert_eq!(comps.normalv, Tuple::vector(0., 0., -1.));
|
||||
///
|
||||
/// // The hit should offset the point.
|
||||
/// let r = Ray::new(Tuple::point(0., 0., -5.), Tuple::vector(0., 0., 1.));
|
||||
/// let mut shape = Sphere::default();
|
||||
/// shape .set_transform(Matrix4x4::translation(0.,0.,1.));
|
||||
/// let i = Intersection::new(5., &shape);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// assert!(comps.over_point.z< -EPSILON/2.);
|
||||
/// assert!(comps.point.z>comps.over_point.z);
|
||||
/// ```
|
||||
pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData<'i> {
|
||||
let point = r.position(i.t);
|
||||
@ -206,13 +192,10 @@ pub fn prepare_computations<'i>(i: &'i Intersection, r: &Ray) -> PrecomputedData
|
||||
} else {
|
||||
(false, normalv)
|
||||
};
|
||||
|
||||
let over_point = point + normalv * EPSILON;
|
||||
PrecomputedData {
|
||||
t: i.t,
|
||||
object: i.object,
|
||||
point,
|
||||
over_point,
|
||||
normalv,
|
||||
inside,
|
||||
eyev,
|
||||
|
||||
@ -11,29 +11,7 @@ pub mod tuples;
|
||||
pub mod world;
|
||||
|
||||
/// Value considered close enough for PartialEq implementations.
|
||||
pub const EPSILON: Float = 0.00001;
|
||||
pub const EPSILON: f32 = 0.00001;
|
||||
|
||||
pub const BLACK: tuples::Color = tuples::Color::new(0., 0., 0.);
|
||||
pub const WHITE: tuples::Color = tuples::Color::new(1., 1., 1.);
|
||||
|
||||
#[cfg(feature = "float-as-double")]
|
||||
/// submodule to defined types, constants and methods when `Float` is defined as a `f64` using the
|
||||
/// "float-as-double" cargo feature.
|
||||
pub mod float {
|
||||
pub use std::f64::*;
|
||||
/// Alias of the `f64` type, to be used through out the codebase anywhere a default sized
|
||||
/// `Float` is necessary.
|
||||
pub type Float = f64;
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "float-as-double"))]
|
||||
/// submodule to defined types, constants and methods when `Float` is defined as a `f32` when not using the
|
||||
/// "float-as-double" cargo feature.
|
||||
pub mod float {
|
||||
pub use std::f32::*;
|
||||
/// Alias of the `f32` type, to be used through out the codebase anywhere a default sized
|
||||
/// `Float` is necessary.
|
||||
pub type Float = f32;
|
||||
}
|
||||
|
||||
pub use float::Float;
|
||||
|
||||
@ -2,15 +2,15 @@ use crate::{
|
||||
lights::PointLight,
|
||||
tuples::Color,
|
||||
tuples::{dot, reflect, Tuple},
|
||||
Float, BLACK, WHITE,
|
||||
BLACK, WHITE,
|
||||
};
|
||||
#[derive(Debug, PartialEq, Clone)]
|
||||
pub struct Material {
|
||||
pub color: Color,
|
||||
pub ambient: Float,
|
||||
pub diffuse: Float,
|
||||
pub specular: Float,
|
||||
pub shininess: Float,
|
||||
pub ambient: f32,
|
||||
pub diffuse: f32,
|
||||
pub specular: f32,
|
||||
pub shininess: f32,
|
||||
}
|
||||
|
||||
impl Default for Material {
|
||||
@ -51,10 +51,9 @@ impl Default for Material {
|
||||
/// lights::PointLight,
|
||||
/// materials::{lighting, Material},
|
||||
/// tuples::{Color, Tuple},
|
||||
/// Float, WHITE,
|
||||
/// WHITE,
|
||||
/// };
|
||||
///
|
||||
/// let in_shadow = false;
|
||||
/// let m = Material::default();
|
||||
/// let position = Tuple::point(0., 0., 0.);
|
||||
///
|
||||
@ -62,43 +61,35 @@ impl Default for Material {
|
||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
||||
/// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv);
|
||||
/// assert_eq!(result, Color::new(1.9, 1.9, 1.9));
|
||||
///
|
||||
/// // Lighting with the eye between the light and the surface, eye offset 45°.
|
||||
/// let eyev = Tuple::vector(0., (2. as Float).sqrt() / 2., -(2. as Float).sqrt() / 2.);
|
||||
/// let eyev = Tuple::vector(0., 2_f32.sqrt() / 2., -2_f32.sqrt() / 2.);
|
||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
||||
/// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv);
|
||||
/// assert_eq!(result, WHITE);
|
||||
///
|
||||
/// // Lighting with the eye opposite surface, light offset 45°.
|
||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
||||
/// let light = PointLight::new(Tuple::point(0., 10., -10.), WHITE);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv);
|
||||
/// assert_eq!(result, Color::new(0.7364, 0.7364, 0.7364));
|
||||
///
|
||||
/// // Lighting with the eye in the path of the reflection vector.
|
||||
/// let eyev = Tuple::vector(0., -(2.0 as Float).sqrt() / 2., -(2.0 as Float).sqrt() / 2.);
|
||||
/// let eyev = Tuple::vector(0., -2_f32.sqrt() / 2., -2_f32.sqrt() / 2.);
|
||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
||||
/// let light = PointLight::new(Tuple::point(0., 10., -10.), WHITE);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
||||
/// assert_eq!(result, Color::new(1.63639, 1.63639, 1.63639));
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv);
|
||||
/// assert_eq!(result, Color::new(1.6363853, 1.6363853, 1.6363853));
|
||||
///
|
||||
/// // Lighting with the light behind the surface.
|
||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
||||
/// let light = PointLight::new(Tuple::point(0., 0., 10.), WHITE);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
||||
/// assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
||||
///
|
||||
/// // Lighting with the surface in shadow.
|
||||
/// let in_shadow = true;
|
||||
/// let eyev = Tuple::vector(0., 0., -1.);
|
||||
/// let normalv = Tuple::vector(0., 0., -1.);
|
||||
/// let light = PointLight::new(Tuple::point(0., 0., -10.), WHITE);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv, in_shadow);
|
||||
/// let result = lighting(&m, &light, position, eyev, normalv);
|
||||
/// assert_eq!(result, Color::new(0.1, 0.1, 0.1));
|
||||
/// ```
|
||||
pub fn lighting(
|
||||
@ -107,7 +98,6 @@ pub fn lighting(
|
||||
point: Tuple,
|
||||
eyev: Tuple,
|
||||
normalv: Tuple,
|
||||
in_shadow: bool,
|
||||
) -> Color {
|
||||
// Combine the surface color with the light's color.
|
||||
let effective_color = material.color * light.intensity;
|
||||
@ -138,9 +128,5 @@ pub fn lighting(
|
||||
};
|
||||
(diffuse, specular)
|
||||
};
|
||||
if in_shadow {
|
||||
ambient
|
||||
} else {
|
||||
ambient + diffuse + specular
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,15 +1,15 @@
|
||||
use std::fmt;
|
||||
use std::ops::{Index, IndexMut, Mul, Sub};
|
||||
|
||||
use crate::{tuples::Tuple, Float, EPSILON};
|
||||
use crate::{tuples::Tuple, EPSILON};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Matrix2x2 {
|
||||
m: [[Float; 2]; 2],
|
||||
m: [[f32; 2]; 2],
|
||||
}
|
||||
impl Matrix2x2 {
|
||||
/// Create a `Matrix2x2` with each of the given rows.
|
||||
pub fn new(r0: [Float; 2], r1: [Float; 2]) -> Matrix2x2 {
|
||||
pub fn new(r0: [f32; 2], r1: [f32; 2]) -> Matrix2x2 {
|
||||
Matrix2x2 { m: [r0, r1] }
|
||||
}
|
||||
|
||||
@ -24,13 +24,13 @@ impl Matrix2x2 {
|
||||
///
|
||||
/// assert_eq!(a.determinant(), 17.);
|
||||
/// ```
|
||||
pub fn determinant(&self) -> Float {
|
||||
pub fn determinant(&self) -> f32 {
|
||||
let m = self;
|
||||
m[(0, 0)] * m[(1, 1)] - m[(0, 1)] * m[(1, 0)]
|
||||
}
|
||||
}
|
||||
impl Index<(usize, usize)> for Matrix2x2 {
|
||||
type Output = Float;
|
||||
type Output = f32;
|
||||
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
|
||||
&self.m[row][col]
|
||||
}
|
||||
@ -53,11 +53,11 @@ impl PartialEq for Matrix2x2 {
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Matrix3x3 {
|
||||
m: [[Float; 3]; 3],
|
||||
m: [[f32; 3]; 3],
|
||||
}
|
||||
impl Matrix3x3 {
|
||||
/// Create a `Matrix3x2` with each of the given rows.
|
||||
pub fn new(r0: [Float; 3], r1: [Float; 3], r2: [Float; 3]) -> Matrix3x3 {
|
||||
pub fn new(r0: [f32; 3], r1: [f32; 3], r2: [f32; 3]) -> Matrix3x3 {
|
||||
Matrix3x3 { m: [r0, r1, r2] }
|
||||
}
|
||||
/// submatrix extracts a 2x2 matrix ignoring the 0-based `row` and `col` given.
|
||||
@ -101,7 +101,7 @@ impl Matrix3x3 {
|
||||
/// assert_eq!(b.determinant(), 25.0);
|
||||
/// assert_eq!(b.determinant(), a.minor(1, 0));
|
||||
/// ```
|
||||
pub fn minor(&self, row: usize, col: usize) -> Float {
|
||||
pub fn minor(&self, row: usize, col: usize) -> f32 {
|
||||
self.submatrix(row, col).determinant()
|
||||
}
|
||||
|
||||
@ -117,7 +117,7 @@ impl Matrix3x3 {
|
||||
/// assert_eq!(a.minor(1, 0), 25.);
|
||||
/// assert_eq!(a.cofactor(1, 0), -25.);
|
||||
/// ```
|
||||
pub fn cofactor(&self, row: usize, col: usize) -> Float {
|
||||
pub fn cofactor(&self, row: usize, col: usize) -> f32 {
|
||||
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
|
||||
self.submatrix(row, col).determinant() * negate
|
||||
}
|
||||
@ -134,12 +134,12 @@ impl Matrix3x3 {
|
||||
/// assert_eq!(a.cofactor(0, 2), -46.);
|
||||
/// assert_eq!(a.determinant(), -196.);
|
||||
/// ```
|
||||
pub fn determinant(&self) -> Float {
|
||||
pub fn determinant(&self) -> f32 {
|
||||
(0..3).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
|
||||
}
|
||||
}
|
||||
impl Index<(usize, usize)> for Matrix3x3 {
|
||||
type Output = Float;
|
||||
type Output = f32;
|
||||
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
|
||||
&self.m[row][col]
|
||||
}
|
||||
@ -166,7 +166,9 @@ impl PartialEq for Matrix3x3 {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple};
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
||||
///
|
||||
/// // Individual transformations are applied in sequence.
|
||||
/// let p = Tuple::point(1., 0., 1.);
|
||||
@ -193,11 +195,11 @@ impl PartialEq for Matrix3x3 {
|
||||
/// ```
|
||||
#[derive(Copy, Clone, Default)]
|
||||
pub struct Matrix4x4 {
|
||||
m: [[Float; 4]; 4],
|
||||
m: [[f32; 4]; 4],
|
||||
}
|
||||
|
||||
impl From<[Float; 16]> for Matrix4x4 {
|
||||
fn from(t: [Float; 16]) -> Self {
|
||||
impl From<[f32; 16]> for Matrix4x4 {
|
||||
fn from(t: [f32; 16]) -> Self {
|
||||
Matrix4x4 {
|
||||
m: [
|
||||
[t[0], t[1], t[2], t[3]],
|
||||
@ -236,7 +238,7 @@ impl Matrix4x4 {
|
||||
}
|
||||
|
||||
/// Create a `Matrix4x4` with each of the given rows.
|
||||
pub fn new(r0: [Float; 4], r1: [Float; 4], r2: [Float; 4], r3: [Float; 4]) -> Matrix4x4 {
|
||||
pub fn new(r0: [f32; 4], r1: [f32; 4], r2: [f32; 4], r3: [f32; 4]) -> Matrix4x4 {
|
||||
Matrix4x4 {
|
||||
m: [r0, r1, r2, r3],
|
||||
}
|
||||
@ -259,7 +261,7 @@ impl Matrix4x4 {
|
||||
/// let v = Tuple::vector(-3., 4., 5.);
|
||||
/// assert_eq!(transform * v, v);
|
||||
/// ```
|
||||
pub fn translation(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
||||
pub fn translation(x: f32, y: f32, z: f32) -> Matrix4x4 {
|
||||
Matrix4x4::new(
|
||||
[1., 0., 0., x],
|
||||
[0., 1., 0., y],
|
||||
@ -293,7 +295,7 @@ impl Matrix4x4 {
|
||||
/// let p = Tuple::point(2., 3., 4.);
|
||||
/// assert_eq!(transform * p, Tuple::point(-2., 3., 4.));
|
||||
/// ```
|
||||
pub fn scaling(x: Float, y: Float, z: Float) -> Matrix4x4 {
|
||||
pub fn scaling(x: f32, y: f32, z: f32) -> Matrix4x4 {
|
||||
Matrix4x4::new(
|
||||
[x, 0., 0., 0.],
|
||||
[0., y, 0., 0.],
|
||||
@ -307,7 +309,9 @@ impl Matrix4x4 {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float};
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
||||
///
|
||||
/// // A scaling matrix applied to a point.
|
||||
/// let p = Tuple::point(0., 1., 0.);
|
||||
@ -316,11 +320,11 @@ impl Matrix4x4 {
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// half_quarter * p,
|
||||
/// Tuple::point(0., (2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2.)
|
||||
/// Tuple::point(0., 2_f32.sqrt() / 2., 2_f32.sqrt() / 2.)
|
||||
/// );
|
||||
/// assert_eq!(full_quarter * p, Tuple::point(0., 0., 1.),);
|
||||
/// ```
|
||||
pub fn rotation_x(radians: Float) -> Matrix4x4 {
|
||||
pub fn rotation_x(radians: f32) -> Matrix4x4 {
|
||||
let r = radians;
|
||||
Matrix4x4::new(
|
||||
[1., 0., 0., 0.],
|
||||
@ -335,7 +339,9 @@ impl Matrix4x4 {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float};
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
||||
///
|
||||
/// // A scaling matrix applied to a point.
|
||||
/// let p = Tuple::point(0., 0., 1.);
|
||||
@ -344,11 +350,11 @@ impl Matrix4x4 {
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// half_quarter * p,
|
||||
/// Tuple::point((2.0 as Float).sqrt() / 2., 0., (2.0 as Float).sqrt() / 2.)
|
||||
/// Tuple::point(2_f32.sqrt() / 2., 0., 2_f32.sqrt() / 2.)
|
||||
/// );
|
||||
/// assert_eq!(full_quarter * p, Tuple::point(1., 0., 0.,),);
|
||||
/// ```
|
||||
pub fn rotation_y(radians: Float) -> Matrix4x4 {
|
||||
pub fn rotation_y(radians: f32) -> Matrix4x4 {
|
||||
let r = radians;
|
||||
Matrix4x4::new(
|
||||
[r.cos(), 0., r.sin(), 0.],
|
||||
@ -363,7 +369,9 @@ impl Matrix4x4 {
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// use rtchallenge::{float::consts::PI, matrices::Matrix4x4, tuples::Tuple, Float};
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// use rtchallenge::{matrices::Matrix4x4, tuples::Tuple};
|
||||
///
|
||||
/// // A scaling matrix applied to a point.
|
||||
/// let p = Tuple::point(0., 1., 0.);
|
||||
@ -372,11 +380,11 @@ impl Matrix4x4 {
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// half_quarter * p,
|
||||
/// Tuple::point(-(2.0 as Float).sqrt() / 2., (2.0 as Float).sqrt() / 2., 0.)
|
||||
/// Tuple::point(-2_f32.sqrt() / 2., 2_f32.sqrt() / 2., 0.)
|
||||
/// );
|
||||
/// assert_eq!(full_quarter * p, Tuple::point(-1., 0., 0.,),);
|
||||
/// ```
|
||||
pub fn rotation_z(radians: Float) -> Matrix4x4 {
|
||||
pub fn rotation_z(radians: f32) -> Matrix4x4 {
|
||||
let r = radians;
|
||||
Matrix4x4::new(
|
||||
[r.cos(), -r.sin(), 0., 0.],
|
||||
@ -454,7 +462,7 @@ impl Matrix4x4 {
|
||||
/// let p = Tuple::point(2.,3.,4.);
|
||||
/// assert_eq!(transform * p, Tuple::point(2.,3.,7.));
|
||||
|
||||
pub fn shearing(xy: Float, xz: Float, yx: Float, yz: Float, zx: Float, zy: Float) -> Matrix4x4 {
|
||||
pub fn shearing(xy: f32, xz: f32, yx: f32, yz: f32, zx: f32, zy: f32) -> Matrix4x4 {
|
||||
Matrix4x4::new(
|
||||
[1., xy, xz, 0.],
|
||||
[yx, 1., yz, 0.],
|
||||
@ -473,7 +481,7 @@ impl Matrix4x4 {
|
||||
/// use rtchallenge::matrices::Matrix4x4;
|
||||
///
|
||||
/// let i = Matrix4x4::identity();
|
||||
/// assert_eq!(i.inverse_rtiow() * i, i);
|
||||
/// assert_eq!(i.inverse_old() * i, i);
|
||||
///
|
||||
/// let m = Matrix4x4::new(
|
||||
/// [2., 0., 0., 0.],
|
||||
@ -481,10 +489,10 @@ impl Matrix4x4 {
|
||||
/// [0., 0., 4., 0.],
|
||||
/// [0., 0., 0., 1.],
|
||||
/// );
|
||||
/// assert_eq!(m.inverse_rtiow() * m, i);
|
||||
/// assert_eq!(m * m.inverse_rtiow(), i);
|
||||
/// assert_eq!(m.inverse_old() * m, i);
|
||||
/// assert_eq!(m * m.inverse_old(), i);
|
||||
/// ```
|
||||
pub fn inverse_rtiow(&self) -> Matrix4x4 {
|
||||
pub fn inverse_old(&self) -> Matrix4x4 {
|
||||
// TODO(wathiede): how come the C++ version doesn't need to deal with non-invertable
|
||||
// matrix.
|
||||
let mut indxc: [usize; 4] = Default::default();
|
||||
@ -495,7 +503,7 @@ impl Matrix4x4 {
|
||||
for i in 0..4 {
|
||||
let mut irow: usize = 0;
|
||||
let mut icol: usize = 0;
|
||||
let mut big: Float = 0.;
|
||||
let mut big: f32 = 0.;
|
||||
// Choose pivot
|
||||
for j in 0..4 {
|
||||
if ipiv[j] != 1 {
|
||||
@ -530,7 +538,7 @@ impl Matrix4x4 {
|
||||
}
|
||||
|
||||
// Set $m[icol][icol]$ to one by scaling row _icol_ appropriately
|
||||
let pivinv: Float = minv[icol][icol].recip();
|
||||
let pivinv: f32 = minv[icol][icol].recip();
|
||||
minv[icol][icol] = 1.;
|
||||
for j in 0..4 {
|
||||
minv[icol][j] *= pivinv;
|
||||
@ -598,11 +606,11 @@ impl Matrix4x4 {
|
||||
}
|
||||
|
||||
/// Compute minor of a 4x4 matrix.
|
||||
pub fn minor(&self, row: usize, col: usize) -> Float {
|
||||
pub fn minor(&self, row: usize, col: usize) -> f32 {
|
||||
self.submatrix(row, col).determinant()
|
||||
}
|
||||
/// Compute cofactor of a 4x4 matrix.
|
||||
pub fn cofactor(&self, row: usize, col: usize) -> Float {
|
||||
pub fn cofactor(&self, row: usize, col: usize) -> f32 {
|
||||
let negate = if (row + col) % 2 == 0 { 1. } else { -1. };
|
||||
self.submatrix(row, col).determinant() * negate
|
||||
}
|
||||
@ -624,7 +632,7 @@ impl Matrix4x4 {
|
||||
/// assert_eq!(a.cofactor(0, 3), 51.);
|
||||
/// assert_eq!(a.determinant(), -4071.);
|
||||
/// ```
|
||||
pub fn determinant(&self) -> Float {
|
||||
pub fn determinant(&self) -> f32 {
|
||||
(0..4).map(|i| self.cofactor(0, i) * self[(0, i)]).sum()
|
||||
}
|
||||
|
||||
@ -735,9 +743,6 @@ impl Matrix4x4 {
|
||||
/// assert_eq!(c * b.inverse(), a);
|
||||
/// ```
|
||||
pub fn inverse(&self) -> Matrix4x4 {
|
||||
self.inverse_rtc()
|
||||
}
|
||||
pub fn inverse_rtc(&self) -> Matrix4x4 {
|
||||
let m = self;
|
||||
if !m.invertable() {
|
||||
panic!("Matrix4x4::inverse called on matrix with determinant() == 0");
|
||||
@ -865,7 +870,7 @@ impl PartialEq for Matrix4x4 {
|
||||
}
|
||||
|
||||
impl Index<(usize, usize)> for Matrix4x4 {
|
||||
type Output = Float;
|
||||
type Output = f32;
|
||||
fn index(&self, (row, col): (usize, usize)) -> &Self::Output {
|
||||
&self.m[row][col]
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
use crate::{matrices::Matrix4x4, tuples::Tuple, Float};
|
||||
use crate::{matrices::Matrix4x4, tuples::Tuple};
|
||||
|
||||
/// Rays have an origin and a direction. This datatype is the 'ray' in 'raytracer'.
|
||||
pub struct Ray {
|
||||
@ -38,7 +38,7 @@ impl Ray {
|
||||
/// assert_eq!(r.position(-1.), Tuple::point(1., 3., 4.));
|
||||
/// assert_eq!(r.position(2.5), Tuple::point(4.5, 3., 4.));
|
||||
/// ```
|
||||
pub fn position(&self, t: Float) -> Tuple {
|
||||
pub fn position(&self, t: f32) -> Tuple {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
|
||||
|
||||
@ -54,7 +54,7 @@ impl Sphere {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{matrices::Matrix4x4, spheres::Sphere, tuples::Tuple,Float};
|
||||
/// use rtchallenge::{matrices::Matrix4x4, spheres::Sphere, tuples::Tuple};
|
||||
///
|
||||
/// // Normal on X-axis
|
||||
/// let s = Sphere::default();
|
||||
@ -74,20 +74,20 @@ impl Sphere {
|
||||
/// // Normal on a sphere at a nonaxial point.
|
||||
/// let s = Sphere::default();
|
||||
/// let n = s.normal_at(Tuple::point(
|
||||
/// (3. as Float).sqrt() / 3.,
|
||||
/// (3. as Float).sqrt() / 3.,
|
||||
/// (3. as Float).sqrt() / 3.,
|
||||
/// 3_f32.sqrt() / 3.,
|
||||
/// 3_f32.sqrt() / 3.,
|
||||
/// 3_f32.sqrt() / 3.,
|
||||
/// ));
|
||||
/// assert_eq!(
|
||||
/// n,
|
||||
/// Tuple::vector((3. as Float).sqrt() / 3., (3. as Float).sqrt() / 3., (3. as Float).sqrt() / 3.,)
|
||||
/// Tuple::vector(3_f32.sqrt() / 3., 3_f32.sqrt() / 3., 3_f32.sqrt() / 3.,)
|
||||
/// );
|
||||
/// // Normals returned are normalized.
|
||||
/// let s = Sphere::default();
|
||||
/// let n = s.normal_at(Tuple::point(
|
||||
/// (3. as Float).sqrt() / 3.,
|
||||
/// (3. as Float).sqrt() / 3.,
|
||||
/// (3. as Float).sqrt() / 3.,
|
||||
/// 3_f32.sqrt() / 3.,
|
||||
/// 3_f32.sqrt() / 3.,
|
||||
/// 3_f32.sqrt() / 3.,
|
||||
/// ));
|
||||
/// assert_eq!(n, n.normalize());
|
||||
///
|
||||
@ -98,14 +98,14 @@ impl Sphere {
|
||||
/// assert_eq!(n, Tuple::vector(0., 0.70711, -0.70711));
|
||||
|
||||
/// // Compute the normal on a transformed sphere.
|
||||
/// use rtchallenge::float::consts::PI;
|
||||
/// use std::f32::consts::PI;
|
||||
///
|
||||
/// let mut s = Sphere::default();
|
||||
/// s.set_transform ( Matrix4x4::scaling(1.,0.5,1.) * Matrix4x4::rotation_z(PI/5.));
|
||||
/// let n = s.normal_at(Tuple::point(0., (2. as Float).sqrt()/2., -(2. as Float).sqrt()/2.));
|
||||
/// let n = s.normal_at(Tuple::point(0., 2_f32.sqrt()/2., -2_f32.sqrt()/2.));
|
||||
/// assert_eq!(n, Tuple::vector(0., 0.97014, -0.24254));
|
||||
/// ```
|
||||
#[cfg(not(feature = "disable-inverse-cache"))]
|
||||
#[cfg(not(feature = "disable_inverse_cache"))]
|
||||
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
||||
let object_point = self.inverse_transform * world_point;
|
||||
let object_normal = object_point - Tuple::point(0., 0., 0.);
|
||||
@ -113,7 +113,7 @@ impl Sphere {
|
||||
world_normal.w = 0.;
|
||||
world_normal.normalize()
|
||||
}
|
||||
#[cfg(feature = "disable-inverse-cache")]
|
||||
#[cfg(feature = "disable_inverse_cache")]
|
||||
pub fn normal_at(&self, world_point: Tuple) -> Tuple {
|
||||
let object_point = self.transform.inverse() * world_point;
|
||||
let object_normal = object_point - Tuple::point(0., 0., 0.);
|
||||
@ -193,10 +193,6 @@ impl Sphere {
|
||||
/// assert_eq!(xs.len(), 0);
|
||||
/// ```
|
||||
pub fn intersect<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
|
||||
intersect_rtc(sphere, ray)
|
||||
}
|
||||
|
||||
fn intersect_rtc<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
|
||||
let ray = ray.transform(sphere.inverse_transform);
|
||||
let sphere_to_ray = ray.origin - Tuple::point(0., 0., 0.);
|
||||
let a = dot(ray.direction, ray.direction);
|
||||
@ -204,25 +200,11 @@ fn intersect_rtc<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
|
||||
let c = dot(sphere_to_ray, sphere_to_ray) - 1.;
|
||||
let discriminant = b * b - 4. * a * c;
|
||||
if discriminant < 0. {
|
||||
return Intersections::default();
|
||||
}
|
||||
Intersections::default()
|
||||
} else {
|
||||
Intersections::new(vec![
|
||||
Intersection::new((-b - discriminant.sqrt()) / (2. * a), &sphere),
|
||||
Intersection::new((-b + discriminant.sqrt()) / (2. * a), &sphere),
|
||||
])
|
||||
}
|
||||
fn intersect_rtiow<'s>(sphere: &'s Sphere, ray: &Ray) -> Intersections<'s> {
|
||||
let ray = ray.transform(sphere.inverse_transform);
|
||||
let oc = ray.origin - Tuple::point(0., 0., 0.);
|
||||
let a = dot(ray.direction, ray.direction);
|
||||
let b = dot(oc, ray.direction);
|
||||
let c = dot(oc, oc) - 1.;
|
||||
let discriminant = b * b - a * c;
|
||||
if discriminant < 0. {
|
||||
return Intersections::default();
|
||||
}
|
||||
Intersections::new(vec![
|
||||
Intersection::new((-b - discriminant.sqrt()) / a, &sphere),
|
||||
Intersection::new((-b + discriminant.sqrt()) / a, &sphere),
|
||||
])
|
||||
}
|
||||
|
||||
@ -1,25 +1,25 @@
|
||||
use std::ops::{Add, Div, Mul, Neg, Sub};
|
||||
|
||||
use crate::{Float, EPSILON};
|
||||
use crate::EPSILON;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct Tuple {
|
||||
pub x: Float,
|
||||
pub y: Float,
|
||||
pub z: Float,
|
||||
pub w: Float,
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
pub w: f32,
|
||||
}
|
||||
|
||||
impl Tuple {
|
||||
pub fn point(x: Float, y: Float, z: Float) -> Tuple {
|
||||
pub fn point(x: f32, y: f32, z: f32) -> Tuple {
|
||||
Tuple::new(x, y, z, 1.0)
|
||||
}
|
||||
|
||||
pub fn vector(x: Float, y: Float, z: Float) -> Tuple {
|
||||
pub fn vector(x: f32, y: f32, z: f32) -> Tuple {
|
||||
Tuple::new(x, y, z, 0.0)
|
||||
}
|
||||
|
||||
pub fn new(x: Float, y: Float, z: Float, w: Float) -> Tuple {
|
||||
pub fn new(x: f32, y: f32, z: f32, w: f32) -> Tuple {
|
||||
Tuple { x, y, z, w }
|
||||
}
|
||||
|
||||
@ -31,7 +31,7 @@ impl Tuple {
|
||||
self.w == 0.0
|
||||
}
|
||||
|
||||
pub fn magnitude(&self) -> Float {
|
||||
pub fn magnitude(&self) -> f32 {
|
||||
(self.x * self.x + self.y * self.y + self.z * self.z + self.w * self.w).sqrt()
|
||||
}
|
||||
|
||||
@ -50,10 +50,7 @@ impl Tuple {
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// tuples::{reflect, Tuple},
|
||||
/// Float,
|
||||
/// };
|
||||
/// use rtchallenge::tuples::{reflect, Tuple};
|
||||
///
|
||||
/// // Reflecting a vector approaching at 45°
|
||||
/// let v = Tuple::vector(1., -1., 0.);
|
||||
@ -63,7 +60,7 @@ impl Tuple {
|
||||
///
|
||||
/// // Reflecting off a slanted surface.
|
||||
/// let v = Tuple::vector(0., -1., 0.);
|
||||
/// let n = Tuple::vector((2. as Float).sqrt() / 2., (2. as Float).sqrt() / 2., 0.);
|
||||
/// let n = Tuple::vector(2_f32.sqrt() / 2., 2_f32.sqrt() / 2., 0.);
|
||||
/// let r = reflect(v, n);
|
||||
/// assert_eq!(r, Tuple::vector(1., 0., 0.));
|
||||
/// ```
|
||||
@ -83,9 +80,9 @@ impl Add for Tuple {
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Float> for Tuple {
|
||||
impl Div<f32> for Tuple {
|
||||
type Output = Self;
|
||||
fn div(self, rhs: Float) -> Self::Output {
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
Self::Output {
|
||||
x: self.x / rhs,
|
||||
y: self.y / rhs,
|
||||
@ -95,9 +92,9 @@ impl Div<Float> for Tuple {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Float> for Tuple {
|
||||
impl Mul<f32> for Tuple {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: Float) -> Self::Output {
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Self::Output {
|
||||
x: self.x * rhs,
|
||||
y: self.y * rhs,
|
||||
@ -107,7 +104,7 @@ impl Mul<Float> for Tuple {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Tuple> for Float {
|
||||
impl Mul<Tuple> for f32 {
|
||||
type Output = Tuple;
|
||||
fn mul(self, rhs: Tuple) -> Self::Output {
|
||||
Self::Output {
|
||||
@ -150,7 +147,7 @@ impl PartialEq for Tuple {
|
||||
&& ((self.w - rhs.w).abs() < EPSILON)
|
||||
}
|
||||
}
|
||||
pub fn dot(a: Tuple, b: Tuple) -> Float {
|
||||
pub fn dot(a: Tuple, b: Tuple) -> f32 {
|
||||
a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w
|
||||
}
|
||||
pub fn cross(a: Tuple, b: Tuple) -> Tuple {
|
||||
@ -163,12 +160,12 @@ pub fn cross(a: Tuple, b: Tuple) -> Tuple {
|
||||
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Color {
|
||||
pub red: Float,
|
||||
pub green: Float,
|
||||
pub blue: Float,
|
||||
pub red: f32,
|
||||
pub green: f32,
|
||||
pub blue: f32,
|
||||
}
|
||||
impl Color {
|
||||
pub const fn new(red: Float, green: Float, blue: Float) -> Color {
|
||||
pub const fn new(red: f32, green: f32, blue: f32) -> Color {
|
||||
Color { red, green, blue }
|
||||
}
|
||||
}
|
||||
@ -190,9 +187,9 @@ impl Add for Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Float> for Color {
|
||||
impl Div<f32> for Color {
|
||||
type Output = Self;
|
||||
fn div(self, rhs: Float) -> Self::Output {
|
||||
fn div(self, rhs: f32) -> Self::Output {
|
||||
Self::Output {
|
||||
red: self.red / rhs,
|
||||
green: self.green / rhs,
|
||||
@ -201,9 +198,9 @@ impl Div<Float> for Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Float> for Color {
|
||||
impl Mul<f32> for Color {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: Float) -> Self::Output {
|
||||
fn mul(self, rhs: f32) -> Self::Output {
|
||||
Self::Output {
|
||||
red: self.red * rhs,
|
||||
green: self.green * rhs,
|
||||
@ -212,7 +209,7 @@ impl Mul<Float> for Color {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Color> for Float {
|
||||
impl Mul<Color> for f32 {
|
||||
type Output = Color;
|
||||
fn mul(self, rhs: Color) -> Self::Output {
|
||||
Self::Output {
|
||||
@ -256,7 +253,7 @@ impl Sub for Color {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{cross, dot, Color, Float, Tuple, EPSILON};
|
||||
use super::{cross, dot, Color, Tuple, EPSILON};
|
||||
#[test]
|
||||
fn is_point() {
|
||||
// A tuple with w = 1 is a point
|
||||
@ -344,11 +341,8 @@ mod tests {
|
||||
assert_eq!(1., Tuple::vector(1., 0., 0.).magnitude());
|
||||
assert_eq!(1., Tuple::vector(0., 1., 0.).magnitude());
|
||||
assert_eq!(1., Tuple::vector(0., 0., 1.).magnitude());
|
||||
assert_eq!((14. as Float).sqrt(), Tuple::vector(1., 2., 3.).magnitude());
|
||||
assert_eq!(
|
||||
(14. as Float).sqrt(),
|
||||
Tuple::vector(-1., -2., -3.).magnitude()
|
||||
);
|
||||
assert_eq!(14_f32.sqrt(), Tuple::vector(1., 2., 3.).magnitude());
|
||||
assert_eq!(14_f32.sqrt(), Tuple::vector(-1., -2., -3.).magnitude());
|
||||
}
|
||||
#[test]
|
||||
fn vector_normalize() {
|
||||
@ -357,11 +351,7 @@ mod tests {
|
||||
Tuple::vector(4., 0., 0.).normalize()
|
||||
);
|
||||
assert_eq!(
|
||||
Tuple::vector(
|
||||
1. / (14. as Float).sqrt(),
|
||||
2. / (14. as Float).sqrt(),
|
||||
3. / (14. as Float).sqrt()
|
||||
),
|
||||
Tuple::vector(1. / 14_f32.sqrt(), 2. / 14_f32.sqrt(), 3. / 14_f32.sqrt()),
|
||||
Tuple::vector(1., 2., 3.).normalize()
|
||||
);
|
||||
}
|
||||
|
||||
@ -91,9 +91,7 @@ impl World {
|
||||
/// use rtchallenge::{
|
||||
/// intersections::{prepare_computations, Intersection},
|
||||
/// lights::PointLight,
|
||||
/// matrices::Matrix4x4,
|
||||
/// rays::Ray,
|
||||
/// spheres::Sphere,
|
||||
/// tuples::{Color, Tuple},
|
||||
/// world::World,
|
||||
/// WHITE,
|
||||
@ -117,31 +115,16 @@ impl World {
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let c = w.shade_hit(&comps);
|
||||
/// assert_eq!(c, Color::new(0.90498, 0.90498, 0.90498));
|
||||
///
|
||||
/// // Shading with an intersection in shadow.
|
||||
/// let mut w = World::default();
|
||||
/// w.light = Some(PointLight::new(Tuple::point(0., 0., -10.), WHITE));
|
||||
/// let s1 = Sphere::default();
|
||||
/// let mut s2 = Sphere::default();
|
||||
/// s2.set_transform(Matrix4x4::translation(0., 0., 10.));
|
||||
/// w.objects = vec![s1, s2.clone()];
|
||||
/// let r = Ray::new(Tuple::point(0., 0., 5.), Tuple::vector(0., 0., 1.));
|
||||
/// let i = Intersection::new(4., &s2);
|
||||
/// let comps = prepare_computations(&i, &r);
|
||||
/// let c = w.shade_hit(&comps);
|
||||
/// assert_eq!(c, Color::new(0.1, 0.1, 0.1));
|
||||
/// ```
|
||||
pub fn shade_hit(&self, comps: &PrecomputedData) -> Color {
|
||||
// TODO(wathiede): support multiple light sources by iterating over all
|
||||
// the light sources and summing the calls to lighting.
|
||||
let shadowed = self.is_shadowed(comps.over_point);
|
||||
lighting(
|
||||
&comps.object.material,
|
||||
&self.light.as_ref().expect("World has no lights"),
|
||||
comps.over_point,
|
||||
comps.point,
|
||||
comps.eyev,
|
||||
comps.normalv,
|
||||
shadowed,
|
||||
)
|
||||
}
|
||||
/// Compute color for given ray fired at the world.
|
||||
@ -192,48 +175,4 @@ impl World {
|
||||
None => BLACK,
|
||||
}
|
||||
}
|
||||
|
||||
/// Determine if point in world is in a shadow.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use rtchallenge::{
|
||||
/// tuples::{ Tuple},
|
||||
/// world::World,
|
||||
/// };
|
||||
///
|
||||
/// let w = World::test_world();
|
||||
///
|
||||
/// // There is no shadow when nothing is collinear with point and light.
|
||||
/// let p = Tuple::point(0.,10.,0.);
|
||||
/// assert_eq!(w.is_shadowed(p), false);
|
||||
///
|
||||
/// // There shadow when an object is between the point and the light.
|
||||
/// let p = Tuple::point(10.,-10.,10.);
|
||||
/// assert_eq!(w.is_shadowed(p), true);
|
||||
///
|
||||
/// // There is no shadow when an object is behind the light.
|
||||
/// let p = Tuple::point(-20.,20.,-20.);
|
||||
/// assert_eq!(w.is_shadowed(p), false);
|
||||
///
|
||||
/// // There is no shadow when an object is behind the point.
|
||||
/// let p = Tuple::point(-2.,2.,-2.);
|
||||
/// assert_eq!(w.is_shadowed(p), false);
|
||||
pub fn is_shadowed(&self, point: Tuple) -> bool {
|
||||
// TODO(wathiede): how to make this multi light aware?
|
||||
let light = self
|
||||
.light
|
||||
.as_ref()
|
||||
.expect("cannot compute is_shadowed in world with no light");
|
||||
let v = light.position - point;
|
||||
let distance = v.magnitude();
|
||||
let direction = v.normalize();
|
||||
|
||||
let r = Ray::new(point, direction);
|
||||
let intersections = self.intersect(&r);
|
||||
if let Some(h) = intersections.hit() {
|
||||
return h.t < distance;
|
||||
}
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user