Compare commits
5 Commits
master
...
github-bui
| Author | SHA1 | Date |
|---|---|---|
|
|
8c4a0002fe | |
|
|
a700a28d9c | |
|
|
f52d201ffd | |
|
|
ea5e9c3870 | |
|
|
32f5939458 |
|
|
@ -1,2 +0,0 @@
|
|||
[build]
|
||||
rustflags = "-C target-cpu=native"
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
*.png filter=lfs diff=lfs merge=lfs -text
|
||||
test_data/stanford_bunny.obj filter=lfs diff=lfs merge=lfs -text
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
|
@ -8,11 +8,7 @@ jobs:
|
|||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
- uses: actions/checkout@v1
|
||||
- name: Install dependencies
|
||||
run: sudo apt-get update && sudo apt-get install libsdl2-dev
|
||||
- name: Build
|
||||
|
|
|
|||
|
|
@ -1,9 +1,5 @@
|
|||
*~
|
||||
|
||||
# Linux perf results
|
||||
perf.data
|
||||
perf.data.*
|
||||
|
||||
# Generated by Cargo
|
||||
# will have compiled files and executables
|
||||
/target/
|
||||
|
|
@ -21,4 +17,4 @@ Cargo.lock
|
|||
#already existing elements are commented out
|
||||
|
||||
/target
|
||||
#**/*.rs.bk
|
||||
#**/*.rs.bk
|
||||
|
|
|
|||
36
Cargo.toml
36
Cargo.toml
|
|
@ -4,35 +4,13 @@ version = "0.1.0"
|
|||
authors = ["Matthew Gordon <matthew.scott.gordon@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
itertools = "0.14"
|
||||
obj = "0.9"
|
||||
quickcheck = "0.9"
|
||||
quickcheck_macros = "0.9"
|
||||
rand = "0.7"
|
||||
rayon = "1.3"
|
||||
sdl2 = "0.32"
|
||||
csv = "1.1.3"
|
||||
clap = "2.33"
|
||||
png = "0.16"
|
||||
quickcheck = "0.9"
|
||||
quickcheck_macros = "0.8"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
|
||||
[[bench]]
|
||||
name = "simple_scene"
|
||||
harness = false
|
||||
|
||||
[profile.dev]
|
||||
opt-level = 3
|
||||
|
||||
[profile.bench]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
debug = true
|
||||
|
||||
[profile.release]
|
||||
opt-level = 3
|
||||
lto = true
|
||||
codegen-units = 1
|
||||
[dependencies.nalgebra]
|
||||
version = "0.19"
|
||||
features = ["arbitrary"]
|
||||
28
README.md
28
README.md
|
|
@ -1,26 +1,2 @@
|
|||

|
||||
|
||||
# Vanrijn
|
||||
|
||||
This project is very much a work-in-progress and at this point.
|
||||
|
||||
Vanrijn is (or at least will be) a [physically based](https://en.wikipedia.org/wiki/Physically_based_rendering)
|
||||
[ray tracer](https://en.wikipedia.org/wiki/Ray_tracing_(graphics)). Many thanks to the
|
||||
authors of the book
|
||||
["Physically Based Rendering: From Theory to Implementation](https://www.pbrt.org/) from
|
||||
which many of the algorithms used here are taken. This is, however _not_ a Rust port of
|
||||
the C++ PBRT rederer described in that book.
|
||||
|
||||
This crate is structured as a library; main.rs is just a glorified test harness which
|
||||
shows an example of using the library to render a scene. It uses SDL2 to display the
|
||||
rendered image.
|
||||
|
||||
On Ubuntu 19.04, if you have the libsdl2-dev package installed you
|
||||
should be able to run `cargo run --release -- --size 800 600` and see
|
||||
a window with a test scene rendered into it. In theory it should work
|
||||
on any platform with SDL2 installed but I've only tested it on Ubuntu
|
||||
Linux.
|
||||
|
||||

|
||||

|
||||

|
||||
# vanrijn
|
||||
Ray tracer
|
||||
|
|
|
|||
|
|
@ -1,53 +0,0 @@
|
|||
use criterion::{criterion_group, criterion_main, Criterion};
|
||||
|
||||
use vanrijn::colour::{ColourRgbF, NamedColour, Spectrum};
|
||||
use vanrijn::materials::ReflectiveMaterial;
|
||||
use vanrijn::math::Vec3;
|
||||
use vanrijn::mesh::load_obj;
|
||||
use vanrijn::partial_render_scene;
|
||||
use vanrijn::raycasting::BoundingVolumeHierarchy;
|
||||
use vanrijn::scene::Scene;
|
||||
use vanrijn::util::Tile;
|
||||
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn simple_scene(bencher: &mut Criterion) {
|
||||
let image_width = 6;
|
||||
let image_height = 6;
|
||||
|
||||
let model_file_path =
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/stanford_bunny.obj");
|
||||
|
||||
bencher.bench_function("simple_scene", |b| {
|
||||
let scene = Scene {
|
||||
camera_location: Vec3::new(-2.0, 1.0, -5.0),
|
||||
objects: vec![Box::new(BoundingVolumeHierarchy::build(
|
||||
load_obj(
|
||||
&model_file_path,
|
||||
Arc::new(ReflectiveMaterial {
|
||||
colour: Spectrum::reflection_from_linear_rgb(&ColourRgbF::from_named(
|
||||
NamedColour::Yellow,
|
||||
)),
|
||||
diffuse_strength: 0.05,
|
||||
reflection_strength: 0.9,
|
||||
}),
|
||||
)
|
||||
.unwrap()
|
||||
.as_mut_slice(),
|
||||
))],
|
||||
};
|
||||
b.iter(|| {
|
||||
let tile = Tile {
|
||||
start_column: 0,
|
||||
end_column: image_width,
|
||||
start_row: 0,
|
||||
end_row: image_height,
|
||||
};
|
||||
partial_render_scene(&scene, tile, image_height, image_width);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
criterion_group!(benches, simple_scene);
|
||||
criterion_main!(benches);
|
||||
|
|
@ -1 +0,0 @@
|
|||
nightly
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
use crate::colour::{ColourXyz, Photon};
|
||||
use crate::image::{ImageRgbU8, ToneMapper};
|
||||
use crate::util::{Array2D, Tile};
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct AccumulationBuffer {
|
||||
colour_buffer: Array2D<ColourXyz>,
|
||||
colour_sum_buffer: Array2D<ColourXyz>,
|
||||
colour_bias_buffer: Array2D<ColourXyz>,
|
||||
weight_buffer: Array2D<f64>,
|
||||
weight_bias_buffer: Array2D<f64>,
|
||||
}
|
||||
|
||||
impl AccumulationBuffer {
|
||||
pub fn new(height: usize, width: usize) -> AccumulationBuffer {
|
||||
let colour_buffer = Array2D::new(width, height);
|
||||
let colour_sum_buffer = Array2D::new(width, height);
|
||||
let colour_bias_buffer = Array2D::new(width, height);
|
||||
let weight_buffer = Array2D::new(width, height);
|
||||
let weight_bias_buffer = Array2D::new(width, height);
|
||||
AccumulationBuffer {
|
||||
colour_buffer,
|
||||
colour_sum_buffer,
|
||||
colour_bias_buffer,
|
||||
weight_buffer,
|
||||
weight_bias_buffer,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn width(&self) -> usize {
|
||||
self.colour_buffer.get_width()
|
||||
}
|
||||
|
||||
pub fn height(&self) -> usize {
|
||||
self.colour_buffer.get_height()
|
||||
}
|
||||
|
||||
pub fn to_image_rgb_u8<Op: ToneMapper<ColourXyz>>(&self, tone_mapper: &Op) -> ImageRgbU8 {
|
||||
let mut result = ImageRgbU8::new(self.width(), self.height());
|
||||
tone_mapper.apply_tone_mapping(&self.colour_buffer, &mut result);
|
||||
result
|
||||
}
|
||||
|
||||
pub fn update_pixel(&mut self, row: usize, column: usize, photon: &Photon, weight: f64) {
|
||||
let buffer_colour = &mut self.colour_buffer[row][column];
|
||||
let buffer_colour_sum = &mut self.colour_sum_buffer[row][column];
|
||||
let buffer_colour_bias = &mut self.colour_bias_buffer[row][column];
|
||||
let buffer_weight = &mut self.weight_buffer[row][column];
|
||||
let buffer_weight_bias = &mut self.weight_bias_buffer[row][column];
|
||||
let photon_colour = ColourXyz::from_photon(photon);
|
||||
let weight_sum_y = weight - *buffer_weight_bias;
|
||||
let weight_sum_t = *buffer_weight + weight_sum_y;
|
||||
*buffer_weight_bias = (weight_sum_t - *buffer_weight) - weight_sum_y;
|
||||
*buffer_weight = weight_sum_t;
|
||||
let colour_sum_y = photon_colour.values * weight - buffer_colour_bias.values;
|
||||
let colour_sum_t = buffer_colour_sum.values + colour_sum_y;
|
||||
buffer_colour_bias.values = (colour_sum_t - buffer_colour_sum.values) - colour_sum_y;
|
||||
buffer_colour_sum.values = colour_sum_t;
|
||||
buffer_colour.values = buffer_colour_sum.values / *buffer_weight;
|
||||
}
|
||||
|
||||
pub fn merge_tile(&mut self, tile: &Tile, src: &AccumulationBuffer) {
|
||||
assert!(tile.width() == src.width());
|
||||
assert!(tile.height() == src.height());
|
||||
for i in 0..tile.height() {
|
||||
for j in 0..tile.width() {
|
||||
let dst_colour = &mut self.colour_buffer[tile.start_row + i][tile.start_column + j];
|
||||
let dst_weight = &mut self.weight_buffer[tile.start_row + i][tile.start_column + j];
|
||||
*dst_colour = blend(
|
||||
dst_colour,
|
||||
*dst_weight,
|
||||
&src.colour_buffer[i][j],
|
||||
src.weight_buffer[i][j],
|
||||
);
|
||||
*dst_weight += src.weight_buffer[i][j];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn blend(colour1: &ColourXyz, weight1: f64, colour2: &ColourXyz, weight2: f64) -> ColourXyz {
|
||||
ColourXyz {
|
||||
values: (colour1.values * weight1 + colour2.values * weight2) * (1.0 / (weight1 + weight2)),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn has_expected_width() {
|
||||
let target = AccumulationBuffer::new(16, 12);
|
||||
assert!(target.width() == 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn has_expected_height() {
|
||||
let target = AccumulationBuffer::new(16, 12);
|
||||
assert!(target.height() == 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_pixel_does_not_panic_inside_bounds() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
for i in 0..12 {
|
||||
for j in 0..16 {
|
||||
target.update_pixel(i, j, &Default::default(), 1.0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn update_pixel_panics_when_row_to_large() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
target.update_pixel(12, 0, &Default::default(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn update_pixel_panics_when_column_to_large() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
target.update_pixel(0, 16, &Default::default(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_update_sets_expected_value() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
let photon = Photon {
|
||||
wavelength: 589.0,
|
||||
intensity: 1.5,
|
||||
};
|
||||
let row = 4;
|
||||
let column = 5;
|
||||
let weight = 0.8;
|
||||
target.update_pixel(row, column, &photon, weight);
|
||||
assert!(target.colour_buffer[row][column] == ColourXyz::from_photon(&photon));
|
||||
assert!(target.weight_buffer[row][column] == weight);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_update_only_sets_expected_value() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
let original = target.clone();
|
||||
let photon = Photon {
|
||||
wavelength: 589.0,
|
||||
intensity: 1.5,
|
||||
};
|
||||
let set_row = 4;
|
||||
let set_column = 5;
|
||||
target.update_pixel(set_row, set_column, &photon, 0.8);
|
||||
for i in 0..12 {
|
||||
for j in 0..16 {
|
||||
if i != set_row && j != set_column {
|
||||
assert!(target.colour_buffer[i][j] == original.colour_buffer[i][j]);
|
||||
assert!(target.weight_buffer[i][j] == original.weight_buffer[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_update_blends_colours() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
let photon1 = Photon {
|
||||
wavelength: 589.0,
|
||||
intensity: 0.5,
|
||||
};
|
||||
let photon2 = Photon {
|
||||
wavelength: 656.0,
|
||||
intensity: 1.5,
|
||||
};
|
||||
let colour1 = ColourXyz::from_photon(&photon1);
|
||||
let colour2 = ColourXyz::from_photon(&photon2);
|
||||
let expected_x = (colour1.x() + colour2.x()) / 2.0;
|
||||
let expected_y = (colour1.y() + colour2.y()) / 2.0;
|
||||
let expected_z = (colour1.z() + colour2.z()) / 2.0;
|
||||
let row = 4;
|
||||
let column = 5;
|
||||
target.update_pixel(row, column, &photon1, 1.0);
|
||||
target.update_pixel(row, column, &photon2, 1.0);
|
||||
assert!(target.colour_buffer[row][column].x() == expected_x);
|
||||
assert!(target.colour_buffer[row][column].y() == expected_y);
|
||||
assert!(target.colour_buffer[row][column].z() == expected_z);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn second_update_blends_colours_proportionally() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
let photon1 = Photon {
|
||||
wavelength: 589.0,
|
||||
intensity: 0.5,
|
||||
};
|
||||
let photon2 = Photon {
|
||||
wavelength: 656.0,
|
||||
intensity: 1.5,
|
||||
};
|
||||
let colour1 = ColourXyz::from_photon(&photon1);
|
||||
let colour2 = ColourXyz::from_photon(&photon2);
|
||||
let weight1 = 0.75;
|
||||
let weight2 = 1.25;
|
||||
let expected_x = (colour1.x() * weight1 + colour2.x() * weight2) / (weight1 + weight2);
|
||||
let expected_y = (colour1.y() * weight1 + colour2.y() * weight2) / (weight1 + weight2);
|
||||
let expected_z = (colour1.z() * weight1 + colour2.z() * weight2) / (weight1 + weight2);
|
||||
let row = 4;
|
||||
let column = 5;
|
||||
target.update_pixel(row, column, &photon1, weight1);
|
||||
target.update_pixel(row, column, &photon2, weight2);
|
||||
assert!(target.colour_buffer[row][column].x() == expected_x);
|
||||
assert!(target.colour_buffer[row][column].y() == expected_y);
|
||||
assert!(target.colour_buffer[row][column].z() == expected_z);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn third_update_blends_colours_proportionally() {
|
||||
let mut target = AccumulationBuffer::new(16, 12);
|
||||
let photon1 = Photon {
|
||||
wavelength: 589.0,
|
||||
intensity: 0.5,
|
||||
};
|
||||
let photon2 = Photon {
|
||||
wavelength: 656.0,
|
||||
intensity: 1.5,
|
||||
};
|
||||
let photon3 = Photon {
|
||||
wavelength: 393.0,
|
||||
intensity: 1.2,
|
||||
};
|
||||
let colour1 = ColourXyz::from_photon(&photon1);
|
||||
let colour2 = ColourXyz::from_photon(&photon2);
|
||||
let colour3 = ColourXyz::from_photon(&photon3);
|
||||
let weight1 = 0.75;
|
||||
let weight2 = 1.25;
|
||||
let weight3 = 0.5;
|
||||
let expected_x = (colour1.x() * weight1 + colour2.x() * weight2 + colour3.x() * weight3)
|
||||
/ (weight1 + weight2 + weight3);
|
||||
let expected_y = (colour1.y() * weight1 + colour2.y() * weight2 + colour3.y() * weight3)
|
||||
/ (weight1 + weight2 + weight3);
|
||||
let expected_z = (colour1.z() * weight1 + colour2.z() * weight2 + colour3.z() * weight3)
|
||||
/ (weight1 + weight2 + weight3);
|
||||
let row = 4;
|
||||
let column = 5;
|
||||
target.update_pixel(row, column, &photon1, weight1);
|
||||
target.update_pixel(row, column, &photon2, weight2);
|
||||
target.update_pixel(row, column, &photon3, weight3);
|
||||
assert!(target.colour_buffer[row][column].x() == expected_x);
|
||||
assert!(target.colour_buffer[row][column].y() == expected_y);
|
||||
assert!(target.colour_buffer[row][column].z() == expected_z);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn merge_tile_produces_same_results_as_applying_photons_directly() {
|
||||
let mut single_buffer = AccumulationBuffer::new(16, 12);
|
||||
let mut large_buffer = AccumulationBuffer::new(16, 12);
|
||||
let mut small_buffer = AccumulationBuffer::new(4, 5);
|
||||
let tile = Tile {
|
||||
start_column: 3,
|
||||
end_column: 7,
|
||||
start_row: 4,
|
||||
end_row: 9,
|
||||
};
|
||||
|
||||
for i in 0..12 {
|
||||
for j in 0..16 {
|
||||
let wavelength = 350.0 + (i * j) as f64;
|
||||
let intensity = 1.0;
|
||||
let weight = 0.2 + i as f64 * 0.02 + j as f64 * 0.3;
|
||||
single_buffer.update_pixel(
|
||||
i,
|
||||
j,
|
||||
&Photon {
|
||||
wavelength,
|
||||
intensity,
|
||||
},
|
||||
weight,
|
||||
);
|
||||
large_buffer.update_pixel(
|
||||
i,
|
||||
j,
|
||||
&Photon {
|
||||
wavelength,
|
||||
intensity,
|
||||
},
|
||||
weight,
|
||||
);
|
||||
}
|
||||
}
|
||||
for i in 0..5 {
|
||||
for j in 0..4 {
|
||||
let wavelength = 700.0 - (i * j) as f64;
|
||||
let intensity = 1.0;
|
||||
let weight = 0.2 + i as f64 * 0.02 + j as f64 * 0.3;
|
||||
small_buffer.update_pixel(
|
||||
i,
|
||||
j,
|
||||
&Photon {
|
||||
wavelength,
|
||||
intensity,
|
||||
},
|
||||
weight,
|
||||
);
|
||||
single_buffer.update_pixel(
|
||||
tile.start_row + i,
|
||||
tile.start_column + j,
|
||||
&Photon {
|
||||
wavelength,
|
||||
intensity,
|
||||
},
|
||||
weight,
|
||||
);
|
||||
}
|
||||
}
|
||||
large_buffer.merge_tile(&tile, &small_buffer);
|
||||
|
||||
for i in 0..12 {
|
||||
for j in 0..16 {
|
||||
assert!(
|
||||
(large_buffer.colour_buffer[i][j].values
|
||||
- single_buffer.colour_buffer[i][j].values)
|
||||
.norm()
|
||||
< 0.0000000001
|
||||
);
|
||||
assert!(large_buffer.weight_buffer[i][j] == single_buffer.weight_buffer[i][j]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
use nalgebra::{Matrix3, RealField, Vector3};
|
||||
|
||||
pub fn try_change_of_basis_matrix<T: RealField>(
|
||||
x: &Vector3<T>,
|
||||
y: &Vector3<T>,
|
||||
z: &Vector3<T>,
|
||||
) -> Option<Matrix3<T>> {
|
||||
Some(Matrix3::from_rows(&[x.transpose(), y.transpose(), z.transpose()]))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod change_of_basis_matrix {
|
||||
use super::*;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[test]
|
||||
fn produces_isentity_when_passed_axes() {
|
||||
let target: Matrix3<f32> = try_change_of_basis_matrix(
|
||||
&Vector3::x_axis(),
|
||||
&Vector3::y_axis(),
|
||||
&Vector3::z_axis(),
|
||||
)
|
||||
.unwrap();
|
||||
assert!(target == Matrix3::identity())
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn swap_xy_does_not_change_z(v: Vector3<f32>) {
|
||||
let target: Matrix3<f32> = try_change_of_basis_matrix(
|
||||
&Vector3::y_axis(),
|
||||
&Vector3::x_axis(),
|
||||
&Vector3::z_axis(),
|
||||
)
|
||||
.unwrap();
|
||||
let v2 = target * v;
|
||||
assert!(v2.z == v.z)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn swap_xy_copies_y_to_x(v: Vector3<f32>) {
|
||||
let target: Matrix3<f32> = try_change_of_basis_matrix(
|
||||
&Vector3::y_axis(),
|
||||
&Vector3::x_axis(),
|
||||
&Vector3::z_axis(),
|
||||
)
|
||||
.unwrap();
|
||||
let v2 = target * v;
|
||||
assert!(v2.x == v.y)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn swap_xy_copies_x_to_y(v: Vector3<f32>) {
|
||||
let target: Matrix3<f32> = try_change_of_basis_matrix(
|
||||
&Vector3::y_axis(),
|
||||
&Vector3::x_axis(),
|
||||
&Vector3::z_axis(),
|
||||
)
|
||||
.unwrap();
|
||||
let v2 = target * v;
|
||||
assert!(v2.y == v.x)
|
||||
}
|
||||
}
|
||||
}
|
||||
186
src/camera.rs
186
src/camera.rs
|
|
@ -1,31 +1,29 @@
|
|||
use crate::math::Vec3;
|
||||
use nalgebra::{convert, RealField, Vector3};
|
||||
|
||||
use super::accumulation_buffer::AccumulationBuffer;
|
||||
use super::colour::Photon;
|
||||
use super::integrators::{Integrator, SimpleRandomIntegrator};
|
||||
use super::colour::{ColourRgbF, NamedColour};
|
||||
use super::image::ImageRgbF;
|
||||
use super::integrators::{DirectionalLight, Integrator, WhittedIntegrator};
|
||||
use super::raycasting::Ray;
|
||||
use super::sampler::Sampler;
|
||||
use super::scene::Scene;
|
||||
use super::util::Tile;
|
||||
|
||||
use rand::random;
|
||||
struct ImageSampler<T: RealField> {
|
||||
image_height_pixels: u32,
|
||||
image_width_pixels: u32,
|
||||
|
||||
struct ImageSampler {
|
||||
image_height_pixels: usize,
|
||||
image_width_pixels: usize,
|
||||
film_width: T,
|
||||
film_height: T,
|
||||
|
||||
film_width: f64,
|
||||
film_height: f64,
|
||||
camera_location: Vec3<f64>,
|
||||
film_distance: f64,
|
||||
camera_location: Vector3<T>,
|
||||
film_distance: T,
|
||||
}
|
||||
|
||||
impl ImageSampler {
|
||||
pub fn new(width: usize, height: usize, camera_location: Vec3<f64>) -> ImageSampler {
|
||||
impl<T: RealField> ImageSampler<T> {
|
||||
pub fn new(width: u32, height: u32, camera_location: Vector3<T>) -> ImageSampler<T> {
|
||||
let (film_width, film_height) = {
|
||||
let width = width as f64;
|
||||
let height = height as f64;
|
||||
let film_size = 1.0;
|
||||
let width: T = convert(width as f64);
|
||||
let height: T = convert(height as f64);
|
||||
let film_size: T = convert(1.0);
|
||||
if width > height {
|
||||
(width / height, film_size)
|
||||
} else {
|
||||
|
|
@ -35,98 +33,87 @@ impl ImageSampler {
|
|||
ImageSampler {
|
||||
image_height_pixels: height,
|
||||
image_width_pixels: width,
|
||||
film_distance: 1.0,
|
||||
film_distance: convert(1.0),
|
||||
film_width,
|
||||
film_height,
|
||||
camera_location,
|
||||
}
|
||||
}
|
||||
|
||||
fn scale(i: usize, n: usize, l: f64) -> f64 {
|
||||
let n = n as f64;
|
||||
let i = i as f64;
|
||||
let pixel_size = l * (1.0 / n);
|
||||
(i + random::<f64>()) * pixel_size
|
||||
fn scale(i: u32, n: u32, l: T) -> T {
|
||||
let one: T = convert(1.0);
|
||||
let n: T = convert(n as f64);
|
||||
let i: T = convert(i as f64);
|
||||
let pixel_size: T = l * (one / n);
|
||||
(i + convert(0.5)) * pixel_size
|
||||
}
|
||||
|
||||
fn ray_for_pixel(&self, row: usize, column: usize) -> Ray {
|
||||
fn ray_for_pixel(&self, row: u32, column: u32) -> Ray<T> {
|
||||
Ray::new(
|
||||
self.camera_location,
|
||||
Vec3::new(
|
||||
Vector3::new(
|
||||
Self::scale(column, self.image_width_pixels, self.film_width)
|
||||
- self.film_width * 0.5,
|
||||
Self::scale(
|
||||
self.image_height_pixels - (row + 1),
|
||||
self.image_height_pixels,
|
||||
self.film_height,
|
||||
) - self.film_height * 0.5,
|
||||
- self.film_width * convert(0.5),
|
||||
Self::scale(row, self.image_height_pixels, self.film_height)
|
||||
- self.film_height * convert(0.5),
|
||||
self.film_distance,
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
const RECURSION_LIMIT: u16 = 128;
|
||||
pub fn render_scene<T: RealField>(output_image: &mut ImageRgbF<T>, scene: &Scene<T>) {
|
||||
partial_render_scene(
|
||||
output_image,
|
||||
scene,
|
||||
0,
|
||||
output_image.get_height(),
|
||||
0,
|
||||
output_image.get_width(),
|
||||
)
|
||||
}
|
||||
|
||||
/// Render a rectangular section of the image.
|
||||
///
|
||||
/// The contents and the image, along with the camera, are defined by `scene`.
|
||||
///
|
||||
/// Assuming an overall image size given by `width` and `height`, the part of the image
|
||||
/// defined by `tile` is rendered and returned. Rendering a tile at a time allows a partially-
|
||||
/// rendered image to be displayed to the user.
|
||||
///
|
||||
/// # Examples
|
||||
//
|
||||
/// ```
|
||||
/// # use vanrijn::math::Vec3;
|
||||
/// # use vanrijn::scene::Scene;
|
||||
/// # use vanrijn::util::TileIterator;
|
||||
/// # use vanrijn::partial_render_scene;
|
||||
/// # let scene = Scene { camera_location: Vec3::new(0.0, 0.0, 0.0), objects: vec![] };
|
||||
/// let image_width = 640;
|
||||
/// let image_height = 480;
|
||||
/// let time_size = 32;
|
||||
/// for tile in TileIterator::new(640, 480, 32) {
|
||||
/// let tile_image = partial_render_scene( &scene, tile, image_height, image_width );
|
||||
/// // display and/or save tile_image
|
||||
/// }
|
||||
/// ```
|
||||
pub fn partial_render_scene(
|
||||
scene: &Scene,
|
||||
tile: Tile,
|
||||
height: usize,
|
||||
width: usize,
|
||||
) -> AccumulationBuffer {
|
||||
let mut output_image_tile = AccumulationBuffer::new(tile.width(), tile.height());
|
||||
let image_sampler = ImageSampler::new(width, height, scene.camera_location);
|
||||
let integrator = SimpleRandomIntegrator {};
|
||||
pub fn partial_render_scene<T: RealField>(
|
||||
output_image: &mut ImageRgbF<T>,
|
||||
scene: &Scene<T>,
|
||||
row_start: u32,
|
||||
row_end: u32,
|
||||
column_start: u32,
|
||||
column_end: u32,
|
||||
) {
|
||||
let image_sampler = ImageSampler::new(
|
||||
output_image.get_width(),
|
||||
output_image.get_height(),
|
||||
scene.camera_location,
|
||||
);
|
||||
let ambient_intensity: T = convert(0.0);
|
||||
let directional_intensity1: T = convert(7.0);
|
||||
let directional_intensity2: T = convert(3.0);
|
||||
let integrator = WhittedIntegrator::<T> {
|
||||
ambient_light: ColourRgbF::from_named(NamedColour::White) * ambient_intensity,
|
||||
lights: vec![
|
||||
DirectionalLight {
|
||||
direction: Vector3::new(convert(1.0), convert(1.0), convert(-1.0)).normalize(),
|
||||
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity1,
|
||||
},
|
||||
DirectionalLight {
|
||||
direction: Vector3::new(convert(-0.5), convert(2.0), convert(-0.5)).normalize(),
|
||||
colour: ColourRgbF::from_named(NamedColour::White) * directional_intensity2,
|
||||
},
|
||||
],
|
||||
};
|
||||
let sampler = Sampler { scene };
|
||||
for column in 0..tile.width() {
|
||||
for row in 0..tile.height() {
|
||||
let ray = image_sampler.ray_for_pixel(tile.start_row + row, tile.start_column + column);
|
||||
for column in column_start..column_end {
|
||||
for row in row_start..row_end {
|
||||
let ray = image_sampler.ray_for_pixel(row, column);
|
||||
let hit = sampler.sample(&ray);
|
||||
let photon = match hit {
|
||||
None => Photon {
|
||||
wavelength: 0.0,
|
||||
intensity: 0.0,
|
||||
},
|
||||
Some(intersection_info) => integrator.integrate(
|
||||
&sampler,
|
||||
&intersection_info,
|
||||
&Photon::random_wavelength(),
|
||||
RECURSION_LIMIT,
|
||||
),
|
||||
let colour = match hit {
|
||||
None => ColourRgbF::from_named(NamedColour::Black),
|
||||
Some(intersection_info) => integrator.integrate(&sampler, &intersection_info),
|
||||
};
|
||||
output_image_tile.update_pixel(
|
||||
row,
|
||||
column,
|
||||
&photon.scale_intensity(Photon::random_wavelength_pdf(photon.wavelength)),
|
||||
1.0,
|
||||
);
|
||||
output_image.set_colour(row, column, colour);
|
||||
}
|
||||
}
|
||||
output_image_tile
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -134,7 +121,7 @@ mod tests {
|
|||
use super::*;
|
||||
use crate::materials::LambertianMaterial;
|
||||
use crate::raycasting::{Intersect, IntersectionInfo, Plane};
|
||||
use std::sync::Arc;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[cfg(test)]
|
||||
mod imagesampler {
|
||||
|
|
@ -143,23 +130,23 @@ mod tests {
|
|||
#[test]
|
||||
fn scale_returns_correct_value_for_zero() {
|
||||
let correct_value = (3.0 / 10.0) / 2.0;
|
||||
assert!((ImageSampler::scale(0, 10, 3.0f64) - correct_value).abs() < 0.5)
|
||||
assert!((ImageSampler::scale(0, 10, 3.0f64) - correct_value).abs() < 0.0000000001)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn scale_returns_correct_value_for_last_pixel() {
|
||||
let correct_value = 3.0 - (3.0 / 10.0) / 2.0;
|
||||
assert!((ImageSampler::scale(9, 10, 3.0f64) - correct_value).abs() < 0.5)
|
||||
assert!((ImageSampler::scale(9, 10, 3.0f64) - correct_value).abs() < 0.0000000001)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_for_pixel_returns_value_that_intersects_film_plane_at_expected_location() {
|
||||
let target = ImageSampler::new(800, 600, Vec3::new(0.0, 0.0, 0.0));
|
||||
let target = ImageSampler::new(800, 600, Vector3::new(0.0, 0.0, 0.0));
|
||||
let ray = target.ray_for_pixel(100, 200);
|
||||
let film_plane = Plane::new(
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
Vector3::new(0.0, 0.0, 1.0),
|
||||
target.film_distance,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
Rc::new(LambertianMaterial::<f64>::new_dummy()),
|
||||
);
|
||||
let point_on_film_plane = match film_plane.intersect(&ray) {
|
||||
Some(IntersectionInfo {
|
||||
|
|
@ -173,12 +160,13 @@ mod tests {
|
|||
}) => location,
|
||||
None => panic!(),
|
||||
};
|
||||
let expected_x = target.film_width * (200.0/800.0 - 0.5);
|
||||
assert!(point_on_film_plane.x() - expected_x < target.film_width / 800.0);
|
||||
assert!(point_on_film_plane.x() - expected_x >= 0.0);
|
||||
let expected_y = -target.film_height * (100.0/600.0 - 0.5);
|
||||
assert!(expected_y - point_on_film_plane.y() < target.film_height / 600.0);
|
||||
assert!(expected_y - point_on_film_plane.y() >= 0.0);
|
||||
let expected_x: f64 =
|
||||
ImageSampler::scale(200, 800, target.film_width) - target.film_width * 0.5;
|
||||
print!("{}, {}", expected_x, point_on_film_plane);
|
||||
assert!((point_on_film_plane.x - expected_x).abs() < 0.0000000001);
|
||||
let expected_y =
|
||||
ImageSampler::scale(100, 600, target.film_height) - target.film_height * 0.5;
|
||||
assert!((point_on_film_plane.y - expected_y).abs() < 0.0000000001);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,56 +1,59 @@
|
|||
use crate::math::Vec3;
|
||||
use nalgebra::{convert, RealField, Vector3};
|
||||
|
||||
use std::ops::{Add, Mul};
|
||||
|
||||
#[derive(Copy, Clone, Debug, Default)]
|
||||
pub struct ColourRgbF {
|
||||
pub values: Vec3<f64>,
|
||||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct ColourRgbF<T: RealField> {
|
||||
values: Vector3<T>,
|
||||
}
|
||||
|
||||
impl ColourRgbF {
|
||||
pub fn new(red: f64, green: f64, blue: f64) -> ColourRgbF {
|
||||
impl<T: RealField> ColourRgbF<T> {
|
||||
pub fn new(red: T, green: T, blue: T) -> ColourRgbF<T> {
|
||||
ColourRgbF {
|
||||
values: Vec3::new(red, green, blue),
|
||||
values: Vector3::new(red, green, blue),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_named(name: NamedColour) -> ColourRgbF {
|
||||
pub fn from_named(name: NamedColour) -> ColourRgbF<T> {
|
||||
let zero: T = convert(0.0);
|
||||
let half: T = convert(0.5);
|
||||
let one: T = convert(1.0);
|
||||
match name {
|
||||
NamedColour::Black => ColourRgbF::new(0.0, 0.0, 0.0),
|
||||
NamedColour::White => ColourRgbF::new(1.0, 1.0, 1.0),
|
||||
NamedColour::Red => ColourRgbF::new(1.0, 0.0, 0.0),
|
||||
NamedColour::Lime => ColourRgbF::new(0.0, 1.0, 0.0),
|
||||
NamedColour::Blue => ColourRgbF::new(0.0, 0.0, 1.0),
|
||||
NamedColour::Yellow => ColourRgbF::new(1.0, 1.0, 0.0),
|
||||
NamedColour::Cyan => ColourRgbF::new(0.0, 1.0, 1.0),
|
||||
NamedColour::Magenta => ColourRgbF::new(1.0, 0.0, 1.0),
|
||||
NamedColour::Gray => ColourRgbF::new(0.5, 0.5, 0.5),
|
||||
NamedColour::Maroon => ColourRgbF::new(0.5, 0.0, 0.0),
|
||||
NamedColour::Olive => ColourRgbF::new(0.5, 0.5, 0.0),
|
||||
NamedColour::Green => ColourRgbF::new(0.0, 0.5, 0.0),
|
||||
NamedColour::Purple => ColourRgbF::new(0.5, 0.0, 0.5),
|
||||
NamedColour::Teal => ColourRgbF::new(0.0, 0.5, 0.5),
|
||||
NamedColour::Navy => ColourRgbF::new(0.0, 0.0, 0.5),
|
||||
NamedColour::Black => ColourRgbF::new(zero, zero, zero),
|
||||
NamedColour::White => ColourRgbF::new(one, one, one),
|
||||
NamedColour::Red => ColourRgbF::new(one, zero, zero),
|
||||
NamedColour::Lime => ColourRgbF::new(zero, one, zero),
|
||||
NamedColour::Blue => ColourRgbF::new(zero, zero, one),
|
||||
NamedColour::Yellow => ColourRgbF::new(one, one, zero),
|
||||
NamedColour::Cyan => ColourRgbF::new(zero, one, one),
|
||||
NamedColour::Magenta => ColourRgbF::new(one, zero, one),
|
||||
NamedColour::Gray => ColourRgbF::new(half, half, half),
|
||||
NamedColour::Maroon => ColourRgbF::new(half, zero, zero),
|
||||
NamedColour::Olive => ColourRgbF::new(half, half, zero),
|
||||
NamedColour::Green => ColourRgbF::new(zero, half, zero),
|
||||
NamedColour::Purple => ColourRgbF::new(half, zero, half),
|
||||
NamedColour::Teal => ColourRgbF::new(zero, half, half),
|
||||
NamedColour::Navy => ColourRgbF::new(zero, zero, half),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_vec3(v: &Vec3<f64>) -> ColourRgbF {
|
||||
pub fn from_vector3(v: &Vector3<T>) -> ColourRgbF<T> {
|
||||
ColourRgbF { values: *v }
|
||||
}
|
||||
|
||||
pub fn red(&self) -> f64 {
|
||||
self.values.x()
|
||||
pub fn red(&self) -> T {
|
||||
self.values[0]
|
||||
}
|
||||
|
||||
pub fn green(&self) -> f64 {
|
||||
self.values.y()
|
||||
pub fn green(&self) -> T {
|
||||
self.values[1]
|
||||
}
|
||||
|
||||
pub fn blue(&self) -> f64 {
|
||||
self.values.z()
|
||||
pub fn blue(&self) -> T {
|
||||
self.values[2]
|
||||
}
|
||||
|
||||
pub fn as_vec3(&self) -> &Vec3<f64> {
|
||||
pub fn as_vector3(&self) -> &Vector3<T> {
|
||||
&self.values
|
||||
}
|
||||
}
|
||||
|
|
@ -77,27 +80,27 @@ pub enum NamedColour {
|
|||
Navy,
|
||||
}
|
||||
|
||||
impl Add<ColourRgbF> for ColourRgbF {
|
||||
type Output = ColourRgbF;
|
||||
fn add(self, rhs: ColourRgbF) -> ColourRgbF {
|
||||
impl<T: RealField> Add<ColourRgbF<T>> for ColourRgbF<T> {
|
||||
type Output = ColourRgbF<T>;
|
||||
fn add(self, rhs: ColourRgbF<T>) -> ColourRgbF<T> {
|
||||
ColourRgbF {
|
||||
values: self.values + rhs.values,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for ColourRgbF {
|
||||
type Output = ColourRgbF;
|
||||
fn mul(self, rhs: f64) -> ColourRgbF {
|
||||
impl<T: RealField> Mul<T> for ColourRgbF<T> {
|
||||
type Output = ColourRgbF<T>;
|
||||
fn mul(self, rhs: T) -> ColourRgbF<T> {
|
||||
ColourRgbF {
|
||||
values: self.values * rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<ColourRgbF> for ColourRgbF {
|
||||
type Output = ColourRgbF;
|
||||
fn mul(self, rhs: ColourRgbF) -> ColourRgbF {
|
||||
impl<T: RealField> Mul<ColourRgbF<T>> for ColourRgbF<T> {
|
||||
type Output = ColourRgbF<T>;
|
||||
fn mul(self, rhs: ColourRgbF<T>) -> ColourRgbF<T> {
|
||||
ColourRgbF {
|
||||
values: self.values.component_mul(&rhs.values),
|
||||
}
|
||||
|
|
@ -112,9 +115,9 @@ mod tests {
|
|||
use super::*;
|
||||
use quickcheck::{Arbitrary, Gen};
|
||||
use quickcheck_macros::quickcheck;
|
||||
impl Arbitrary for ColourRgbF {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> ColourRgbF {
|
||||
let values = <Vec3<f64> as Arbitrary>::arbitrary(g);
|
||||
impl<T: Arbitrary + RealField> Arbitrary for ColourRgbF<T> {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> ColourRgbF<T> {
|
||||
let values = <Vector3<T> as Arbitrary>::arbitrary(g);
|
||||
ColourRgbF { values }
|
||||
}
|
||||
}
|
||||
|
|
@ -130,12 +133,12 @@ mod tests {
|
|||
#[test]
|
||||
fn as_vector3_returns_expected_vector() {
|
||||
let target = ColourRgbF::new(1.0, 2.0, 3.0);
|
||||
let result = target.as_vec3();
|
||||
assert!(result.x() == 1.0);
|
||||
let result = target.as_vector3();
|
||||
assert!(result.x == 1.0);
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn any_colour_multiplied_by_zero_is_black(colour: ColourRgbF) {
|
||||
fn any_colour_multiplied_by_zero_is_black(colour: ColourRgbF<f64>) {
|
||||
let target = colour * 0.0;
|
||||
assert!(target.red() == 0.0);
|
||||
assert!(target.green() == 0.0);
|
||||
|
|
@ -143,14 +146,17 @@ mod tests {
|
|||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn red_channel_multiplied_by_scalar_yields_correct_result(colour: ColourRgbF, scalar: f64) {
|
||||
fn red_channel_multiplied_by_scalar_yields_correct_result(
|
||||
colour: ColourRgbF<f64>,
|
||||
scalar: f64,
|
||||
) {
|
||||
let target = colour * scalar;
|
||||
assert!(target.red() == colour.red() * scalar);
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn green_channel_multiplied_by_scalar_yields_correct_result(
|
||||
colour: ColourRgbF,
|
||||
colour: ColourRgbF<f64>,
|
||||
scalar: f64,
|
||||
) {
|
||||
let target = colour * scalar;
|
||||
|
|
@ -159,7 +165,7 @@ mod tests {
|
|||
|
||||
#[quickcheck]
|
||||
fn blue_channel_multiplied_by_scalar_yields_correct_result(
|
||||
colour: ColourRgbF,
|
||||
colour: ColourRgbF<f64>,
|
||||
scalar: f64,
|
||||
) {
|
||||
let target = colour * scalar;
|
||||
|
|
@ -167,7 +173,10 @@ mod tests {
|
|||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn adding_colourrgbf_adds_individual_channels(colour1: ColourRgbF, colour2: ColourRgbF) {
|
||||
fn adding_colourrgbf_adds_individual_channels(
|
||||
colour1: ColourRgbF<f64>,
|
||||
colour2: ColourRgbF<f64>,
|
||||
) {
|
||||
let target = colour1 + colour2;
|
||||
assert!(target.red() == colour1.red() + colour2.red());
|
||||
assert!(target.green() == colour1.green() + colour2.green());
|
||||
|
|
@ -176,8 +185,8 @@ mod tests {
|
|||
|
||||
#[quickcheck]
|
||||
fn multiplying_colourrgbf_adds_individual_channels(
|
||||
colour1: ColourRgbF,
|
||||
colour2: ColourRgbF,
|
||||
colour1: ColourRgbF<f64>,
|
||||
colour2: ColourRgbF<f64>,
|
||||
) {
|
||||
let target = colour1 * colour2;
|
||||
assert!(target.red() == colour1.red() * colour2.red());
|
||||
|
|
@ -1,134 +0,0 @@
|
|||
use crate::math::{Mat3, Vec3};
|
||||
|
||||
use super::{ColourRgbF, Photon};
|
||||
|
||||
/// A CIE XYZ Colour Value
|
||||
#[derive(Clone, Debug, Default, PartialEq)]
|
||||
pub struct ColourXyz {
|
||||
pub values: Vec3<f64>,
|
||||
}
|
||||
|
||||
impl ColourXyz {
|
||||
/// Construct a ColourXyz with the specified XYZ values
|
||||
pub fn new(x: f64, y: f64, z: f64) -> ColourXyz {
|
||||
ColourXyz {
|
||||
values: Vec3::new(x, y, z),
|
||||
}
|
||||
}
|
||||
|
||||
/// Calculate the XYZ colour of a laser light with the given wavelength
|
||||
///
|
||||
/// The wavelength is in nanometres.
|
||||
pub fn for_wavelength(wavelength: f64) -> ColourXyz {
|
||||
let values = Vec3::new(
|
||||
colour_matching_function_x(wavelength),
|
||||
colour_matching_function_y(wavelength),
|
||||
colour_matching_function_z(wavelength),
|
||||
);
|
||||
ColourXyz { values }
|
||||
}
|
||||
|
||||
pub fn from_photon(photon: &Photon) -> ColourXyz {
|
||||
let mut result = Self::for_wavelength(photon.wavelength);
|
||||
result.values *= photon.intensity;
|
||||
result
|
||||
}
|
||||
|
||||
pub fn x(&self) -> f64 {
|
||||
self.values.x()
|
||||
}
|
||||
|
||||
pub fn y(&self) -> f64 {
|
||||
self.values.y()
|
||||
}
|
||||
|
||||
pub fn z(&self) -> f64 {
|
||||
self.values.z()
|
||||
}
|
||||
|
||||
pub fn to_linear_rgb(&self) -> ColourRgbF {
|
||||
let transform = Mat3::from_rows(
|
||||
&Vec3::new(3.24096994, -1.53738318, -0.49861076),
|
||||
&Vec3::new(-0.96924364, 1.87596750, 0.04155506),
|
||||
&Vec3::new(0.05563008, -0.20397696, 1.05697151),
|
||||
);
|
||||
ColourRgbF::from_vec3(&(transform * self.values))
|
||||
}
|
||||
|
||||
pub fn from_linear_rgb(rgb: &ColourRgbF) -> ColourXyz {
|
||||
let transform = Mat3::from_rows(
|
||||
&Vec3::new(0.41239080, 0.35758434, 0.18048079),
|
||||
&Vec3::new(0.21263901, 0.71516868, 0.07219232),
|
||||
&Vec3::new(0.01933082, 0.11919478, 0.95053215),
|
||||
);
|
||||
ColourXyz {
|
||||
values: transform * rgb.values,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn to_srgb(&self) -> ColourRgbF {
|
||||
let mut srgb = self.to_linear_rgb();
|
||||
for element in srgb.values.coords.iter_mut() {
|
||||
*element = srgb_gamma(*element);
|
||||
}
|
||||
srgb
|
||||
}
|
||||
}
|
||||
|
||||
fn srgb_gamma(u: f64) -> f64 {
|
||||
if u <= 0.0031308 {
|
||||
12.98 * u
|
||||
} else {
|
||||
1.005 * u.powf(1.0 / 2.4) - 0.055
|
||||
}
|
||||
}
|
||||
|
||||
fn gaussian(wavelength: f64, alpha: f64, mu: f64, sigma1: f64, sigma2: f64) -> f64 {
|
||||
let denominator = 2.0 * (if wavelength < mu { sigma1 } else { sigma2 }).powi(2);
|
||||
alpha * (-(wavelength - mu).powi(2) / denominator).exp()
|
||||
}
|
||||
|
||||
fn colour_matching_function_x(wavelength: f64) -> f64 {
|
||||
gaussian(wavelength, 1.056, 599.8, 37.9, 31.0)
|
||||
+ gaussian(wavelength, 0.362, 442.0, 16.0, 26.7)
|
||||
+ gaussian(wavelength, -0.065, 501.1, 20.4, 26.2)
|
||||
}
|
||||
|
||||
fn colour_matching_function_y(wavelength: f64) -> f64 {
|
||||
gaussian(wavelength, 0.821, 568.8, 46.9, 40.5) + gaussian(wavelength, 0.286, 530.9, 16.3, 31.1)
|
||||
}
|
||||
|
||||
fn colour_matching_function_z(wavelength: f64) -> f64 {
|
||||
gaussian(wavelength, 1.217, 437.0, 11.8, 36.0) + gaussian(wavelength, 0.681, 459.0, 26.0, 13.8)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn x_returns_zero_for_default() {
|
||||
let target: ColourXyz = Default::default();
|
||||
assert!(target.x() == 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn x_returns_specified_value_after_constructioni_with_new() {
|
||||
let target = ColourXyz::new(0.1, 0.2, 0.3);
|
||||
assert!(target.x() == 0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn z_returns_specified_value_after_constructioni_with_new() {
|
||||
let target = ColourXyz::new(0.1, 0.2, 0.3);
|
||||
assert!(target.z() == 0.3);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn roundtrip_to_linear_rgb_yields_original_values() {
|
||||
let target = ColourXyz::new(0.1, 0.2, 0.3);
|
||||
let rgb = target.to_linear_rgb();
|
||||
let xyz = ColourXyz::from_linear_rgb(&rgb);
|
||||
assert!((target.values - xyz.values).norm() < 0.00000001);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,14 +0,0 @@
|
|||
pub mod colour_rgb;
|
||||
pub use colour_rgb::{ColourRgbF, ColourRgbU8, NamedColour};
|
||||
|
||||
pub mod photon;
|
||||
pub use photon::Photon;
|
||||
|
||||
pub mod colour_xyz;
|
||||
pub use colour_xyz::ColourXyz;
|
||||
|
||||
pub mod spectrum;
|
||||
pub use spectrum::Spectrum;
|
||||
|
||||
pub const SHORTEST_VISIBLE_WAVELENGTH: f64 = 380.0;
|
||||
pub const LONGEST_VISIBLE_WAVELENGTH: f64 = 740.0;
|
||||
|
|
@ -1,43 +0,0 @@
|
|||
use crate::colour::{LONGEST_VISIBLE_WAVELENGTH, SHORTEST_VISIBLE_WAVELENGTH};
|
||||
|
||||
use rand::random;
|
||||
|
||||
/// A quantum of light with a given wavelength and intensity
|
||||
#[derive(Clone, Default, Debug)]
|
||||
pub struct Photon {
|
||||
/// The wavelength in nanometres
|
||||
pub wavelength: f64,
|
||||
/// The intensity of the light
|
||||
///
|
||||
/// Depending on context, this might represent actual intensity in W/sr,
|
||||
/// radiant flux in W, irradiance in W/m^2, or radiance in W/(m^2sr).
|
||||
pub intensity: f64,
|
||||
}
|
||||
|
||||
impl Photon {
|
||||
pub fn random_wavelength() -> Photon {
|
||||
Photon {
|
||||
wavelength: SHORTEST_VISIBLE_WAVELENGTH
|
||||
+ (LONGEST_VISIBLE_WAVELENGTH - SHORTEST_VISIBLE_WAVELENGTH) * random::<f64>(),
|
||||
intensity: 0.0,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn random_wavelength_pdf(_wavelength: f64) -> f64 {
|
||||
LONGEST_VISIBLE_WAVELENGTH - SHORTEST_VISIBLE_WAVELENGTH
|
||||
}
|
||||
|
||||
pub fn scale_intensity(&self, scale_factor: f64) -> Photon {
|
||||
Photon {
|
||||
wavelength: self.wavelength,
|
||||
intensity: self.intensity * scale_factor,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_intensity(&self, intensity: f64) -> Photon {
|
||||
Photon {
|
||||
wavelength: self.wavelength,
|
||||
intensity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,491 +0,0 @@
|
|||
use crate::colour::{ColourRgbF, Photon, LONGEST_VISIBLE_WAVELENGTH, SHORTEST_VISIBLE_WAVELENGTH};
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Spectrum {
|
||||
shortest_wavelength: f64,
|
||||
longest_wavelength: f64,
|
||||
samples: Vec<f64>,
|
||||
}
|
||||
|
||||
impl Spectrum {
|
||||
pub fn black() -> Spectrum {
|
||||
Spectrum {
|
||||
shortest_wavelength: SHORTEST_VISIBLE_WAVELENGTH,
|
||||
longest_wavelength: LONGEST_VISIBLE_WAVELENGTH,
|
||||
samples: vec![0.0, 0.0],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn grey(brightness: f64) -> Spectrum {
|
||||
Spectrum {
|
||||
shortest_wavelength: SHORTEST_VISIBLE_WAVELENGTH,
|
||||
longest_wavelength: LONGEST_VISIBLE_WAVELENGTH,
|
||||
samples: vec![brightness; 2],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn diamond_index_of_refraction() -> Spectrum {
|
||||
Spectrum {
|
||||
shortest_wavelength: 326.27,
|
||||
longest_wavelength: 774.9,
|
||||
samples: vec![
|
||||
2.505813241,
|
||||
2.487866556,
|
||||
2.473323675,
|
||||
2.464986815,
|
||||
2.455051934,
|
||||
2.441251728,
|
||||
2.431478974,
|
||||
2.427076431,
|
||||
2.420857286,
|
||||
2.411429037,
|
||||
2.406543164,
|
||||
2.406202402,
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
fn wavelength_range(&self) -> f64 {
|
||||
self.longest_wavelength - self.shortest_wavelength
|
||||
}
|
||||
|
||||
fn index_at_or_before_wavelength(&self, wavelength: f64) -> usize {
|
||||
((self.samples.len() - 1) as f64
|
||||
* ((wavelength - self.shortest_wavelength) / self.wavelength_range())) as usize
|
||||
}
|
||||
|
||||
fn wavelength_at_index(&self, index: usize) -> f64 {
|
||||
(index as f64) / ((self.samples.len() - 1) as f64) * self.wavelength_range()
|
||||
+ self.shortest_wavelength
|
||||
}
|
||||
|
||||
pub fn intensity_at_wavelength(&self, wavelength: f64) -> f64 {
|
||||
if wavelength < self.shortest_wavelength || wavelength > self.longest_wavelength {
|
||||
0.0
|
||||
} else {
|
||||
let index_before = self.index_at_or_before_wavelength(wavelength);
|
||||
let wavelength_before = self.wavelength_at_index(index_before);
|
||||
if index_before == self.samples.len() - 1 {
|
||||
self.samples[index_before]
|
||||
} else {
|
||||
let wavelength_after = self.wavelength_at_index(index_before + 1);
|
||||
let delta = wavelength_after - wavelength_before;
|
||||
let ratio = (wavelength - wavelength_before) / delta;
|
||||
self.samples[index_before] * (1.0 - ratio) + self.samples[index_before + 1] * ratio
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn reflection_from_linear_rgb(colour: &ColourRgbF) -> Spectrum {
|
||||
Spectrum {
|
||||
shortest_wavelength: rgb_reference_spectrum::SHORTEST_WAVELENGTH,
|
||||
longest_wavelength: rgb_reference_spectrum::LONGEST_WAVELENGTH,
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
samples: if colour.red() <= colour.green() && colour.red() <= colour.blue() {
|
||||
if colour.green() <= colour.blue() {
|
||||
izip![
|
||||
rgb_reference_spectrum::reflection::WHITE.iter(),
|
||||
rgb_reference_spectrum::reflection::CYAN.iter(),
|
||||
rgb_reference_spectrum::reflection::BLUE.iter()
|
||||
]
|
||||
.map(|(white, cyan, blue)| {
|
||||
colour.red() * white
|
||||
+ (colour.green() - colour.red()) * cyan
|
||||
+ (colour.blue() - colour.green()) * blue
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
izip![
|
||||
rgb_reference_spectrum::reflection::WHITE.iter(),
|
||||
rgb_reference_spectrum::reflection::CYAN.iter(),
|
||||
rgb_reference_spectrum::reflection::GREEN.iter()
|
||||
]
|
||||
.map(|(white, cyan, green)| {
|
||||
colour.red() * white
|
||||
+ (colour.blue() - colour.red()) * cyan
|
||||
+ (colour.green() - colour.blue()) * green
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
} else if colour.green() <= colour.red() && colour.green() < colour.blue() {
|
||||
if colour.red() <= colour.blue() {
|
||||
izip![
|
||||
rgb_reference_spectrum::reflection::WHITE.iter(),
|
||||
rgb_reference_spectrum::reflection::MAGENTA.iter(),
|
||||
rgb_reference_spectrum::reflection::BLUE.iter()
|
||||
]
|
||||
.map(|(white, magenta, blue)| {
|
||||
colour.green() * white
|
||||
+ (colour.red() - colour.green()) * magenta
|
||||
+ (colour.blue() - colour.red()) * blue
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
izip![
|
||||
rgb_reference_spectrum::reflection::WHITE.iter(),
|
||||
rgb_reference_spectrum::reflection::MAGENTA.iter(),
|
||||
rgb_reference_spectrum::reflection::RED.iter()
|
||||
]
|
||||
.map(|(white, magenta, red)| {
|
||||
colour.green() * white
|
||||
+ (colour.blue() - colour.green()) * magenta
|
||||
+ (colour.red() - colour.blue()) * red
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
} else {
|
||||
if colour.red() <= colour.green() {
|
||||
izip![
|
||||
rgb_reference_spectrum::reflection::WHITE.iter(),
|
||||
rgb_reference_spectrum::reflection::YELLOW.iter(),
|
||||
rgb_reference_spectrum::reflection::GREEN.iter()
|
||||
]
|
||||
.map(|(white, yellow, green)| {
|
||||
colour.blue() * white
|
||||
+ (colour.red() - colour.blue()) * yellow
|
||||
+ (colour.green() - colour.red()) * green
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
izip![
|
||||
rgb_reference_spectrum::reflection::WHITE.iter(),
|
||||
rgb_reference_spectrum::reflection::YELLOW.iter(),
|
||||
rgb_reference_spectrum::reflection::RED.iter()
|
||||
]
|
||||
.map(|(white, yellow, red)| {
|
||||
colour.blue() * white
|
||||
+ (colour.green() - colour.blue()) * yellow
|
||||
+ (colour.red() - colour.green()) * red
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
pub fn scale_photon(&self, photon: &Photon) -> Photon {
|
||||
let wavelength = photon.wavelength;
|
||||
photon.scale_intensity(self.intensity_at_wavelength(wavelength))
|
||||
}
|
||||
|
||||
pub fn emit_photon(&self, photon: &Photon) -> Photon {
|
||||
let wavelength = photon.wavelength;
|
||||
photon.set_intensity(self.intensity_at_wavelength(wavelength))
|
||||
}
|
||||
}
|
||||
|
||||
mod rgb_reference_spectrum {
|
||||
pub const SHORTEST_WAVELENGTH: f64 = 380.0;
|
||||
pub const LONGEST_WAVELENGTH: f64 = 720.0;
|
||||
#[allow(clippy::excessive_precision)]
|
||||
pub mod reflection {
|
||||
pub const WHITE: [f64; 32] = [
|
||||
1.0618958571272863e+00,
|
||||
1.0615019980348779e+00,
|
||||
1.0614335379927147e+00,
|
||||
1.0622711654692485e+00,
|
||||
1.0622036218416742e+00,
|
||||
1.0625059965187085e+00,
|
||||
1.0623938486985884e+00,
|
||||
1.0624706448043137e+00,
|
||||
1.0625048144827762e+00,
|
||||
1.0624366131308856e+00,
|
||||
1.0620694238892607e+00,
|
||||
1.0613167586932164e+00,
|
||||
1.0610334029377020e+00,
|
||||
1.0613868564828413e+00,
|
||||
1.0614215366116762e+00,
|
||||
1.0620336151299086e+00,
|
||||
1.0625497454805051e+00,
|
||||
1.0624317487992085e+00,
|
||||
1.0625249140554480e+00,
|
||||
1.0624277664486914e+00,
|
||||
1.0624749854090769e+00,
|
||||
1.0625538581025402e+00,
|
||||
1.0625326910104864e+00,
|
||||
1.0623922312225325e+00,
|
||||
1.0623650980354129e+00,
|
||||
1.0625256476715284e+00,
|
||||
1.0612277619533155e+00,
|
||||
1.0594262608698046e+00,
|
||||
1.0599810758292072e+00,
|
||||
1.0602547314449409e+00,
|
||||
1.0601263046243634e+00,
|
||||
1.0606565756823634e+00,
|
||||
];
|
||||
pub const CYAN: [f64; 32] = [
|
||||
1.0414628021426751e+00,
|
||||
1.0328661533771188e+00,
|
||||
1.0126146228964314e+00,
|
||||
1.0350460524836209e+00,
|
||||
1.0078661447098567e+00,
|
||||
1.0422280385081280e+00,
|
||||
1.0442596738499825e+00,
|
||||
1.0535238290294409e+00,
|
||||
1.0180776226938120e+00,
|
||||
1.0442729908727713e+00,
|
||||
1.0529362541920750e+00,
|
||||
1.0537034271160244e+00,
|
||||
1.0533901869215969e+00,
|
||||
1.0537782700979574e+00,
|
||||
1.0527093770467102e+00,
|
||||
1.0530449040446797e+00,
|
||||
1.0550554640191208e+00,
|
||||
1.0553673610724821e+00,
|
||||
1.0454306634683976e+00,
|
||||
6.2348950639230805e-01,
|
||||
1.8038071613188977e-01,
|
||||
-7.6303759201984539e-03,
|
||||
-1.5217847035781367e-04,
|
||||
-7.5102257347258311e-03,
|
||||
-2.1708639328491472e-03,
|
||||
6.5919466602369636e-04,
|
||||
1.2278815318539780e-02,
|
||||
-4.4669775637208031e-03,
|
||||
1.7119799082865147e-02,
|
||||
4.9211089759759801e-03,
|
||||
5.8762925143334985e-03,
|
||||
2.5259399415550079e-02,
|
||||
];
|
||||
pub const MAGENTA: [f64; 32] = [
|
||||
9.9422138151236850e-01,
|
||||
9.8986937122975682e-01,
|
||||
9.8293658286116958e-01,
|
||||
9.9627868399859310e-01,
|
||||
1.0198955019000133e+00,
|
||||
1.0166395501210359e+00,
|
||||
1.0220913178757398e+00,
|
||||
9.9651666040682441e-01,
|
||||
1.0097766178917882e+00,
|
||||
1.0215422470827016e+00,
|
||||
6.4031953387790963e-01,
|
||||
2.5012379477078184e-03,
|
||||
6.5339939555769944e-03,
|
||||
2.8334080462675826e-03,
|
||||
-5.1209675389074505e-11,
|
||||
-9.0592291646646381e-03,
|
||||
3.3936718323331200e-03,
|
||||
-3.0638741121828406e-03,
|
||||
2.2203936168286292e-01,
|
||||
6.3141140024811970e-01,
|
||||
9.7480985576500956e-01,
|
||||
9.7209562333590571e-01,
|
||||
1.0173770302868150e+00,
|
||||
9.9875194322734129e-01,
|
||||
9.4701725739602238e-01,
|
||||
8.5258623154354796e-01,
|
||||
9.4897798581660842e-01,
|
||||
9.4751876096521492e-01,
|
||||
9.9598944191059791e-01,
|
||||
8.6301351503809076e-01,
|
||||
8.9150987853523145e-01,
|
||||
8.4866492652845082e-01,
|
||||
];
|
||||
pub const YELLOW: [f64; 32] = [
|
||||
5.5740622924920873e-03,
|
||||
-4.7982831631446787e-03,
|
||||
-5.2536564298613798e-03,
|
||||
-6.4571480044499710e-03,
|
||||
-5.9693514658007013e-03,
|
||||
-2.1836716037686721e-03,
|
||||
1.6781120601055327e-02,
|
||||
9.6096355429062641e-02,
|
||||
2.1217357081986446e-01,
|
||||
3.6169133290685068e-01,
|
||||
5.3961011543232529e-01,
|
||||
7.4408810492171507e-01,
|
||||
9.2209571148394054e-01,
|
||||
1.0460304298411225e+00,
|
||||
1.0513824989063714e+00,
|
||||
1.0511991822135085e+00,
|
||||
1.0510530911991052e+00,
|
||||
1.0517397230360510e+00,
|
||||
1.0516043086790485e+00,
|
||||
1.0511944032061460e+00,
|
||||
1.0511590325868068e+00,
|
||||
1.0516612465483031e+00,
|
||||
1.0514038526836869e+00,
|
||||
1.0515941029228475e+00,
|
||||
1.0511460436960840e+00,
|
||||
1.0515123758830476e+00,
|
||||
1.0508871369510702e+00,
|
||||
1.0508923708102380e+00,
|
||||
1.0477492815668303e+00,
|
||||
1.0493272144017338e+00,
|
||||
1.0435963333422726e+00,
|
||||
1.0392280772051465e+00,
|
||||
];
|
||||
pub const RED: [f64; 32] = [
|
||||
1.6575604867086180e-01,
|
||||
1.1846442802747797e-01,
|
||||
1.2408293329637447e-01,
|
||||
1.1371272058349924e-01,
|
||||
7.8992434518899132e-02,
|
||||
3.2205603593106549e-02,
|
||||
-1.0798365407877875e-02,
|
||||
1.8051975516730392e-02,
|
||||
5.3407196598730527e-03,
|
||||
1.3654918729501336e-02,
|
||||
-5.9564213545642841e-03,
|
||||
-1.8444365067353252e-03,
|
||||
-1.0571884361529504e-02,
|
||||
-2.9375521078000011e-03,
|
||||
-1.0790476271835936e-02,
|
||||
-8.0224306697503633e-03,
|
||||
-2.2669167702495940e-03,
|
||||
7.0200240494706634e-03,
|
||||
-8.1528469000299308e-03,
|
||||
6.0772866969252792e-01,
|
||||
9.8831560865432400e-01,
|
||||
9.9391691044078823e-01,
|
||||
1.0039338994753197e+00,
|
||||
9.9234499861167125e-01,
|
||||
9.9926530858855522e-01,
|
||||
1.0084621557617270e+00,
|
||||
9.8358296827441216e-01,
|
||||
1.0085023660099048e+00,
|
||||
9.7451138326568698e-01,
|
||||
9.8543269570059944e-01,
|
||||
9.3495763980962043e-01,
|
||||
9.8713907792319400e-01,
|
||||
];
|
||||
pub const GREEN: [f64; 32] = [
|
||||
2.6494153587602255e-03,
|
||||
-5.0175013429732242e-03,
|
||||
-1.2547236272489583e-02,
|
||||
-9.4554964308388671e-03,
|
||||
-1.2526086181600525e-02,
|
||||
-7.9170697760437767e-03,
|
||||
-7.9955735204175690e-03,
|
||||
-9.3559433444469070e-03,
|
||||
6.5468611982999303e-02,
|
||||
3.9572875517634137e-01,
|
||||
7.5244022299886659e-01,
|
||||
9.6376478690218559e-01,
|
||||
9.9854433855162328e-01,
|
||||
9.9992977025287921e-01,
|
||||
9.9939086751140449e-01,
|
||||
9.9994372267071396e-01,
|
||||
9.9939121813418674e-01,
|
||||
9.9911237310424483e-01,
|
||||
9.6019584878271580e-01,
|
||||
6.3186279338432438e-01,
|
||||
2.5797401028763473e-01,
|
||||
9.4014888527335638e-03,
|
||||
-3.0798345608649747e-03,
|
||||
-4.5230367033685034e-03,
|
||||
-6.8933410388274038e-03,
|
||||
-9.0352195539015398e-03,
|
||||
-8.5913667165340209e-03,
|
||||
-8.3690869120289398e-03,
|
||||
-7.8685832338754313e-03,
|
||||
-8.3657578711085132e-06,
|
||||
5.4301225442817177e-03,
|
||||
-2.7745589759259194e-03,
|
||||
];
|
||||
pub const BLUE: [f64; 32] = [
|
||||
9.9209771469720676e-01,
|
||||
9.8876426059369127e-01,
|
||||
9.9539040744505636e-01,
|
||||
9.9529317353008218e-01,
|
||||
9.9181447411633950e-01,
|
||||
1.0002584039673432e+00,
|
||||
9.9968478437342512e-01,
|
||||
9.9988120766657174e-01,
|
||||
9.8504012146370434e-01,
|
||||
7.9029849053031276e-01,
|
||||
5.6082198617463974e-01,
|
||||
3.3133458513996528e-01,
|
||||
1.3692410840839175e-01,
|
||||
1.8914906559664151e-02,
|
||||
-5.1129770932550889e-06,
|
||||
-4.2395493167891873e-04,
|
||||
-4.1934593101534273e-04,
|
||||
1.7473028136486615e-03,
|
||||
3.7999160177631316e-03,
|
||||
-5.5101474906588642e-04,
|
||||
-4.3716662898480967e-05,
|
||||
7.5874501748732798e-03,
|
||||
2.5795650780554021e-02,
|
||||
3.8168376532500548e-02,
|
||||
4.9489586408030833e-02,
|
||||
4.9595992290102905e-02,
|
||||
4.9814819505812249e-02,
|
||||
3.9840911064978023e-02,
|
||||
3.0501024937233868e-02,
|
||||
2.1243054765241080e-02,
|
||||
6.9596532104356399e-03,
|
||||
4.1733649330980525e-03,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn intensity_at_wavelength_returns_expected_value_at_minimum_wavelength() {
|
||||
let target = Spectrum {
|
||||
shortest_wavelength: 400.5,
|
||||
longest_wavelength: 700.25,
|
||||
samples: vec![0.5, 1.0, 0.75, 1.5],
|
||||
};
|
||||
assert!(target.intensity_at_wavelength(400.5) == 0.5)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intensity_at_wavelength_returns_expected_value_at_max_wavelength() {
|
||||
let target = Spectrum {
|
||||
shortest_wavelength: 400.5,
|
||||
longest_wavelength: 700.25,
|
||||
samples: vec![0.5, 1.0, 0.75, 1.5],
|
||||
};
|
||||
assert!(target.intensity_at_wavelength(700.25) == 1.5)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intensity_at_wavelength_returns_expected_value_at_interior_sample_wavelength() {
|
||||
let target = Spectrum {
|
||||
shortest_wavelength: 400.0,
|
||||
longest_wavelength: 700.0,
|
||||
samples: vec![0.5, 1.0, 0.75, 1.5],
|
||||
};
|
||||
assert!(target.intensity_at_wavelength(500.0) == 1.0);
|
||||
assert!(target.intensity_at_wavelength(600.0) == 0.75);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intensity_at_wavelength_returns_expected_value_at_halfway_between_sample_wavelength() {
|
||||
let target = Spectrum {
|
||||
shortest_wavelength: 400.0,
|
||||
longest_wavelength: 700.0,
|
||||
samples: vec![0.5, 1.0, 0.75, 1.5],
|
||||
};
|
||||
assert!(target.intensity_at_wavelength(450.0) == 0.75);
|
||||
assert!(target.intensity_at_wavelength(550.0) == 0.875);
|
||||
assert!(target.intensity_at_wavelength(650.0) == 1.125);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intensity_below_minimum_wavelength_is_zero() {
|
||||
let target = Spectrum {
|
||||
shortest_wavelength: 400.0,
|
||||
longest_wavelength: 700.0,
|
||||
samples: vec![0.5, 1.0, 0.75, 1.5],
|
||||
};
|
||||
assert!(target.intensity_at_wavelength(399.9999) == 0.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intensity_above_maximum_wavelength_is_zero() {
|
||||
let target = Spectrum {
|
||||
shortest_wavelength: 400.0,
|
||||
longest_wavelength: 700.0,
|
||||
samples: vec![0.5, 1.0, 0.75, 1.5],
|
||||
};
|
||||
assert!(target.intensity_at_wavelength(700.0001) == 0.0);
|
||||
}
|
||||
}
|
||||
222
src/image.rs
222
src/image.rs
|
|
@ -1,105 +1,123 @@
|
|||
use std::fs::File;
|
||||
use std::io::BufWriter;
|
||||
use std::path::Path;
|
||||
use std::convert::TryInto;
|
||||
|
||||
use crate::colour::{ColourRgbF, ColourRgbU8, ColourXyz};
|
||||
use crate::util::Array2D;
|
||||
use nalgebra::{clamp, convert, RealField, Vector3};
|
||||
|
||||
use super::colour::{ColourRgbF, ColourRgbU8};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ImageRgbU8 {
|
||||
data: Array2D<[u8; 3]>,
|
||||
pixel_data: Vec<u8>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl ImageRgbU8 {
|
||||
pub fn new(width: usize, height: usize) -> ImageRgbU8 {
|
||||
pub fn new(width: u32, height: u32) -> ImageRgbU8 {
|
||||
ImageRgbU8 {
|
||||
data: Array2D::new(height, width),
|
||||
width: width,
|
||||
height: height,
|
||||
pixel_data: vec![0; (width * height * 3) as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_colour(&self, row: usize, column: usize) -> ColourRgbU8 {
|
||||
pub fn clear(&mut self) -> &mut ImageRgbU8 {
|
||||
for byte in self.pixel_data.iter_mut() {
|
||||
*byte = 0u8;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_colour(&self, row: u32, column: u32) -> ColourRgbU8 {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = self.calculate_index(row, column);
|
||||
ColourRgbU8 {
|
||||
values: self.data[row][column],
|
||||
values: self.pixel_data[index..index + 3]
|
||||
.try_into()
|
||||
.expect("Wrong length."),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_colour(&mut self, row: usize, column: usize, colour: ColourRgbU8) {
|
||||
let slice = &mut self.data[row][column];
|
||||
slice.copy_from_slice(&colour.values[..]);
|
||||
pub fn set_colour(&mut self, row: u32, column: u32, colour: ColourRgbU8) {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = self.calculate_index(row, column);
|
||||
self.pixel_data[index..index + 3].copy_from_slice(&colour.values[..]);
|
||||
}
|
||||
|
||||
pub fn get_pixel_data(&self) -> &[u8] {
|
||||
let data = self.data.as_slice();
|
||||
unsafe { std::slice::from_raw_parts(data[0].as_ptr(), data.len() * 3) }
|
||||
pub fn get_pixel_data(&self) -> &Vec<u8> {
|
||||
&self.pixel_data
|
||||
}
|
||||
|
||||
pub fn get_width(&self) -> usize {
|
||||
self.data.get_width()
|
||||
pub fn get_width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn get_height(&self) -> usize {
|
||||
self.data.get_height()
|
||||
pub fn get_height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn num_channels() -> usize {
|
||||
pub fn num_channels() -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
pub fn update(&mut self, start_row: usize, start_column: usize, image: &ImageRgbU8) {
|
||||
self.data.update_block(start_row, start_column, &image.data);
|
||||
}
|
||||
|
||||
pub fn write_png(&self, filename: &Path) -> Result<(), std::io::Error> {
|
||||
let file = File::create(filename)?;
|
||||
let file_buffer = &mut BufWriter::new(file);
|
||||
|
||||
let mut encoder = png::Encoder::new(
|
||||
file_buffer,
|
||||
self.get_width() as u32,
|
||||
self.get_height() as u32,
|
||||
);
|
||||
encoder.set_color(png::ColorType::RGB);
|
||||
encoder.set_depth(png::BitDepth::Eight);
|
||||
let mut writer = encoder.write_header()?;
|
||||
writer.write_image_data(self.get_pixel_data())?;
|
||||
Ok(())
|
||||
fn calculate_index(&self, row: u32, column: u32) -> usize {
|
||||
assert!(row < self.height && column < self.width);
|
||||
(((self.height - (row + 1)) * self.width + column) * Self::num_channels()) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ImageRgbF {
|
||||
pub data: Array2D<ColourRgbF>,
|
||||
pub struct ImageRgbF<T: RealField> {
|
||||
pixel_data: Vec<T>,
|
||||
width: u32,
|
||||
height: u32,
|
||||
}
|
||||
|
||||
impl ImageRgbF {
|
||||
pub fn new(width: usize, height: usize) -> ImageRgbF {
|
||||
impl<T: RealField> ImageRgbF<T> {
|
||||
pub fn new(width: u32, height: u32) -> ImageRgbF<T> {
|
||||
ImageRgbF {
|
||||
data: Array2D::new(height, width),
|
||||
width: width,
|
||||
height: height,
|
||||
pixel_data: vec![convert(0.0); (width * height * 3) as usize],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
pub fn clear(&mut self) -> &mut ImageRgbF<T> {
|
||||
for elem in self.pixel_data.iter_mut() {
|
||||
*elem = T::zero();
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
pub fn get_colour(&self, row: usize, column: usize) -> ColourRgbF {
|
||||
self.data[row][column]
|
||||
pub fn get_colour(&self, row: u32, column: u32) -> ColourRgbF<T> {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = self.calculate_index(row, column);
|
||||
ColourRgbF::from_vector3(&Vector3::from_row_slice(&self.pixel_data[index..index + 3]))
|
||||
}
|
||||
|
||||
pub fn set_colour(&mut self, row: usize, column: usize, colour: ColourRgbF) {
|
||||
self.data[row][column] = colour;
|
||||
pub fn set_colour(&mut self, row: u32, column: u32, colour: ColourRgbF<T>) {
|
||||
assert!(row < self.height && column < self.width);
|
||||
let index = self.calculate_index(row, column);
|
||||
self.pixel_data[index..index + 3].copy_from_slice(&colour.as_vector3().as_slice());
|
||||
}
|
||||
|
||||
pub fn get_width(&self) -> usize {
|
||||
self.data.get_width()
|
||||
pub fn get_pixel_data(&self) -> &Vec<T> {
|
||||
&self.pixel_data
|
||||
}
|
||||
|
||||
pub fn get_height(&self) -> usize {
|
||||
self.data.get_height()
|
||||
pub fn get_width(&self) -> u32 {
|
||||
self.width
|
||||
}
|
||||
|
||||
pub fn num_channels() -> usize {
|
||||
pub fn get_height(&self) -> u32 {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn num_channels() -> u32 {
|
||||
3
|
||||
}
|
||||
|
||||
fn calculate_index(&self, row: u32, column: u32) -> usize {
|
||||
assert!(row < self.height && column < self.width);
|
||||
(((self.height - (row + 1)) * self.width + column) * Self::num_channels()) as usize
|
||||
}
|
||||
}
|
||||
|
||||
pub trait NormalizedAsByte {
|
||||
|
|
@ -109,67 +127,47 @@ pub trait NormalizedAsByte {
|
|||
|
||||
impl NormalizedAsByte for f32 {
|
||||
fn normalized_to_byte(self) -> u8 {
|
||||
(self * (u8::MAX as f32)) as u8
|
||||
(self * (std::u8::MAX as f32)) as u8
|
||||
}
|
||||
|
||||
fn byte_to_normalized(byte: u8) -> f32 {
|
||||
(byte as f32) / (u8::MAX as f32)
|
||||
(byte as f32) / (std::u8::MAX as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl NormalizedAsByte for f64 {
|
||||
fn normalized_to_byte(self) -> u8 {
|
||||
(self * (u8::MAX as f64)) as u8
|
||||
(self * (std::u8::MAX as f64)) as u8
|
||||
}
|
||||
|
||||
fn byte_to_normalized(byte: u8) -> f64 {
|
||||
(byte as f64) / (u8::MAX as f64)
|
||||
(byte as f64) / (std::u8::MAX as f64)
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ToneMapper<SourceType> {
|
||||
fn apply_tone_mapping(&self, image_in: &Array2D<SourceType>, image_out: &mut ImageRgbU8);
|
||||
pub trait ToneMapper<T: RealField> {
|
||||
fn apply_tone_mapping(&self, image_in: &ImageRgbF<T>, image_out: &mut ImageRgbU8);
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct ClampingToneMapper {}
|
||||
|
||||
impl ClampingToneMapper {
|
||||
fn clamp(v: &f64) -> u8 {
|
||||
v.clamp(0.0, 1.0).normalized_to_byte()
|
||||
pub fn new() -> ClampingToneMapper {
|
||||
ClampingToneMapper {}
|
||||
}
|
||||
|
||||
fn clamp<T: RealField + NormalizedAsByte>(v: &T) -> u8 {
|
||||
clamp(v, &T::zero(), &T::one()).normalized_to_byte()
|
||||
}
|
||||
}
|
||||
|
||||
impl ToneMapper<ColourRgbF> for ClampingToneMapper {
|
||||
fn apply_tone_mapping(&self, image_in: &Array2D<ColourRgbF>, image_out: &mut ImageRgbU8) {
|
||||
impl<T: RealField + NormalizedAsByte> ToneMapper<T> for ClampingToneMapper {
|
||||
fn apply_tone_mapping(&self, image_in: &ImageRgbF<T>, image_out: &mut ImageRgbU8) {
|
||||
assert!(image_in.get_width() == image_out.get_width());
|
||||
assert!(image_in.get_height() == image_out.get_height());
|
||||
for column in 0..image_in.get_width() {
|
||||
for row in 0..image_in.get_height() {
|
||||
let colour = image_in[row][column];
|
||||
image_out.set_colour(
|
||||
row,
|
||||
column,
|
||||
ColourRgbU8 {
|
||||
values: [
|
||||
Self::clamp(&colour.red()),
|
||||
Self::clamp(&colour.green()),
|
||||
Self::clamp(&colour.blue()),
|
||||
],
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ToneMapper<ColourXyz> for ClampingToneMapper {
|
||||
fn apply_tone_mapping(&self, image_in: &Array2D<ColourXyz>, image_out: &mut ImageRgbU8) {
|
||||
assert!(image_in.get_width() == image_out.get_width());
|
||||
assert!(image_in.get_height() == image_out.get_height());
|
||||
for column in 0..image_in.get_width() {
|
||||
for row in 0..image_in.get_height() {
|
||||
let colour = image_in[row][column].to_srgb();
|
||||
let colour = image_in.get_colour(row, column);
|
||||
image_out.set_colour(
|
||||
row,
|
||||
column,
|
||||
|
|
@ -190,30 +188,6 @@ impl ToneMapper<ColourXyz> for ClampingToneMapper {
|
|||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn get_pixel_data_returns_correct_values() {
|
||||
let mut target = ImageRgbU8::new(4, 3);
|
||||
for i in 0..3 {
|
||||
for j in 0..4 {
|
||||
target.set_colour(
|
||||
i,
|
||||
j,
|
||||
ColourRgbU8 {
|
||||
values: [i as u8, j as u8, i as u8],
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
for i in 0..3 {
|
||||
for j in 0..4 {
|
||||
let index = (i * 4 + j) * 3;
|
||||
assert!(target.get_pixel_data()[index] == i as u8);
|
||||
assert!(target.get_pixel_data()[index + 1] == j as u8);
|
||||
assert!(target.get_pixel_data()[index + 2] == i as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod normalized_as_byte {
|
||||
use super::*;
|
||||
|
||||
|
|
@ -287,7 +261,7 @@ mod tests {
|
|||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(0.0, 0.0, 0.0));
|
||||
target.apply_tone_mapping(&image_in.data, &mut image_out);
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0, 0, 0]);
|
||||
}
|
||||
|
||||
|
|
@ -297,8 +271,8 @@ mod tests {
|
|||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(1.0, 1.0, 1.0));
|
||||
target.apply_tone_mapping(&image_in.data, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0xff, 0xff, 0xff]);
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -307,8 +281,8 @@ mod tests {
|
|||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(2.0, 2.0, 2.0));
|
||||
target.apply_tone_mapping(&image_in.data, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0xff, 0xff, 0xff]);
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0xff, 0xff, 0xff]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -317,8 +291,8 @@ mod tests {
|
|||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(0.0, 2.0, 0.0));
|
||||
target.apply_tone_mapping(&image_in.data, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0x0, 0xff, 0x0]);
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0x0, 0xff, 0x0]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
|
@ -327,8 +301,8 @@ mod tests {
|
|||
let mut image_in = ImageRgbF::new(1, 1);
|
||||
let mut image_out = ImageRgbU8::new(1, 1);
|
||||
image_in.set_colour(0, 0, ColourRgbF::new(0.5, 0.0, 0.0));
|
||||
target.apply_tone_mapping(&image_in.data, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0x7f, 0x0, 0x0]);
|
||||
target.apply_tone_mapping(&image_in, &mut image_out);
|
||||
assert!(image_out.get_colour(0, 0).values == [0x7f, 0x0, 0x0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
use nalgebra::{convert, RealField, Vector3};
|
||||
|
||||
use super::algebra_utils::try_change_of_basis_matrix;
|
||||
use super::colour::ColourRgbF;
|
||||
use super::raycasting::{IntersectionInfo, Ray};
|
||||
use super::sampler::Sampler;
|
||||
|
||||
pub trait Integrator<T: RealField> {
|
||||
fn integrate(&self, sampler: &Sampler<T>, info: &IntersectionInfo<T>) -> ColourRgbF<T>;
|
||||
}
|
||||
|
||||
pub struct DirectionalLight<T: RealField> {
|
||||
pub direction: Vector3<T>,
|
||||
pub colour: ColourRgbF<T>,
|
||||
}
|
||||
|
||||
pub struct WhittedIntegrator<T: RealField> {
|
||||
pub ambient_light: ColourRgbF<T>,
|
||||
pub lights: Vec<DirectionalLight<T>>,
|
||||
}
|
||||
|
||||
// TODO: Get rid of the magic bias number, which should be calculated base on expected error
|
||||
// bounds and tangent direction
|
||||
impl<T: RealField> Integrator<T> for WhittedIntegrator<T> {
|
||||
fn integrate(&self, sampler: &Sampler<T>, info: &IntersectionInfo<T>) -> ColourRgbF<T> {
|
||||
self.lights
|
||||
.iter()
|
||||
.map(|light| {
|
||||
let basis_change =
|
||||
try_change_of_basis_matrix(&info.tangent, &info.cotangent, &info.normal)
|
||||
.expect("Normal, tangent and cotangent don't for a valid basis.");
|
||||
match sampler
|
||||
.sample(&Ray::new(info.location, light.direction).bias(convert(0.000000001)))
|
||||
{
|
||||
Some(_) => self.ambient_light,
|
||||
None => {
|
||||
info.material.bsdf()(
|
||||
basis_change * info.retro,
|
||||
basis_change * light.direction,
|
||||
light.colour,
|
||||
) * light.direction.dot(&info.normal).abs()
|
||||
}
|
||||
}
|
||||
})
|
||||
.fold(self.ambient_light, |a, b| a + b)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
use super::colour::Photon;
|
||||
use super::raycasting::IntersectionInfo;
|
||||
use super::sampler::Sampler;
|
||||
|
||||
mod whitted_integrator;
|
||||
pub use whitted_integrator::*;
|
||||
|
||||
mod simple_random_integrator;
|
||||
pub use simple_random_integrator::*;
|
||||
|
||||
pub trait Integrator {
|
||||
fn integrate(
|
||||
&self,
|
||||
sampler: &Sampler,
|
||||
info: &IntersectionInfo,
|
||||
photon: &Photon,
|
||||
recursion_limit: u16,
|
||||
) -> Photon;
|
||||
}
|
||||
|
|
@ -1,65 +0,0 @@
|
|||
use crate::colour::{ColourRgbF, Photon, Spectrum};
|
||||
use crate::materials::MaterialSampleResult;
|
||||
use crate::math::Vec3;
|
||||
use crate::raycasting::{IntersectionInfo, Ray};
|
||||
use crate::sampler::Sampler;
|
||||
use crate::util::algebra_utils::try_change_of_basis_matrix;
|
||||
|
||||
use super::Integrator;
|
||||
|
||||
pub struct SimpleRandomIntegrator {}
|
||||
|
||||
impl Integrator for SimpleRandomIntegrator {
|
||||
fn integrate(
|
||||
&self,
|
||||
sampler: &Sampler,
|
||||
info: &IntersectionInfo,
|
||||
photon: &Photon,
|
||||
recursion_limit: u16,
|
||||
) -> Photon {
|
||||
if recursion_limit == 0 {
|
||||
return Photon {
|
||||
wavelength: 0.0,
|
||||
intensity: 0.0,
|
||||
};
|
||||
}
|
||||
let world_to_bsdf_space =
|
||||
try_change_of_basis_matrix(&info.tangent, &info.cotangent, &info.normal)
|
||||
.expect("Normal, tangent and cotangent don't form a valid basis.");
|
||||
let bsdf_to_world_space = world_to_bsdf_space
|
||||
.try_inverse()
|
||||
.expect("Expected matrix to be invertable.");
|
||||
let world_space_w_i = info.retro;
|
||||
let w_i = world_to_bsdf_space * world_space_w_i;
|
||||
let MaterialSampleResult {
|
||||
direction: w_o,
|
||||
pdf: w_o_pdf,
|
||||
} = info.material.sample(&w_i, photon);
|
||||
let world_space_w_o = bsdf_to_world_space * w_o;
|
||||
info.material.bsdf()(
|
||||
&w_o,
|
||||
&w_i,
|
||||
&match sampler.sample(&Ray::new(info.location, world_space_w_o).bias(0.000_000_1)) {
|
||||
None => photon.set_intensity(test_lighting_environment(
|
||||
&world_space_w_o,
|
||||
photon.wavelength,
|
||||
)),
|
||||
Some(recursive_hit) => {
|
||||
self.integrate(sampler, &recursive_hit, photon, recursion_limit - 1)
|
||||
}
|
||||
}
|
||||
.scale_intensity(w_o_pdf)
|
||||
.scale_intensity(world_space_w_o.dot(&info.normal).abs()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn test_lighting_environment(w_o: &Vec3<f64>, wavelength: f64) -> f64 {
|
||||
//let sun_direction = Vec3::new(1.0, 1.0, -1.0).normalize();
|
||||
//if w_o.dot(&sun_direction) >= 0.99 {
|
||||
// 300.0
|
||||
//} else {
|
||||
let sky_colour = ColourRgbF::new(w_o.y(), w_o.y(), 1.0);
|
||||
Spectrum::reflection_from_linear_rgb(&sky_colour).intensity_at_wavelength(wavelength)
|
||||
//}
|
||||
}
|
||||
|
|
@ -1,87 +0,0 @@
|
|||
use crate::colour::{Photon, Spectrum};
|
||||
use crate::materials::MaterialSampleResult;
|
||||
use crate::math::Vec3;
|
||||
use crate::raycasting::{IntersectionInfo, Ray};
|
||||
use crate::sampler::Sampler;
|
||||
use crate::util::algebra_utils::try_change_of_basis_matrix;
|
||||
|
||||
use super::Integrator;
|
||||
|
||||
pub struct DirectionalLight {
|
||||
pub direction: Vec3<f64>,
|
||||
pub spectrum: Spectrum,
|
||||
}
|
||||
|
||||
pub struct WhittedIntegrator {
|
||||
pub ambient_light: Spectrum,
|
||||
pub lights: Vec<DirectionalLight>,
|
||||
}
|
||||
|
||||
impl Integrator for WhittedIntegrator {
|
||||
fn integrate(
|
||||
&self,
|
||||
sampler: &Sampler,
|
||||
info: &IntersectionInfo,
|
||||
photon: &Photon,
|
||||
recursion_limit: u16,
|
||||
) -> Photon {
|
||||
let world_to_bsdf_space =
|
||||
try_change_of_basis_matrix(&info.tangent, &info.cotangent, &info.normal)
|
||||
.expect("Normal, tangent and cotangent don't for a valid basis.");
|
||||
let bsdf_to_world_space = world_to_bsdf_space
|
||||
.try_inverse()
|
||||
.expect("Expected matrix to be invertable.");
|
||||
self.lights
|
||||
.iter()
|
||||
.map(|light| {
|
||||
match sampler.sample(&Ray::new(info.location, light.direction).bias(0.000_000_1)) {
|
||||
Some(_) => self.ambient_light.emit_photon(photon),
|
||||
None => info.material.bsdf()(
|
||||
&(world_to_bsdf_space * info.retro),
|
||||
&(world_to_bsdf_space * light.direction),
|
||||
&light
|
||||
.spectrum
|
||||
.emit_photon(photon)
|
||||
.scale_intensity(light.direction.dot(&info.normal).abs()),
|
||||
),
|
||||
}
|
||||
})
|
||||
.chain(
|
||||
[info
|
||||
.material
|
||||
.sample(&(world_to_bsdf_space * info.retro), photon)]
|
||||
.iter()
|
||||
.map(|MaterialSampleResult { direction, pdf: _ }| {
|
||||
let world_space_direction = bsdf_to_world_space * direction;
|
||||
match sampler
|
||||
.sample(&Ray::new(info.location, world_space_direction).bias(0.000_000_1))
|
||||
{
|
||||
Some(recursive_hit) => {
|
||||
if recursion_limit > 0 {
|
||||
let photon = info.material.bsdf()(
|
||||
&(world_to_bsdf_space * info.retro),
|
||||
direction,
|
||||
&self.integrate(
|
||||
sampler,
|
||||
&recursive_hit,
|
||||
photon,
|
||||
recursion_limit - 1,
|
||||
),
|
||||
);
|
||||
photon
|
||||
.scale_intensity(world_space_direction.dot(&info.normal).abs())
|
||||
} else {
|
||||
photon.scale_intensity(0.0)
|
||||
}
|
||||
}
|
||||
None => photon.scale_intensity(0.0),
|
||||
}
|
||||
}),
|
||||
)
|
||||
.fold(photon.clone(), |a, b| {
|
||||
let mut result = a;
|
||||
result.intensity += b.intensity;
|
||||
result
|
||||
})
|
||||
}
|
||||
}
|
||||
13
src/lib.rs
13
src/lib.rs
|
|
@ -1,18 +1,9 @@
|
|||
#![doc = include_str!("../README.md")]
|
||||
|
||||
pub mod accumulation_buffer;
|
||||
mod camera;
|
||||
pub mod algebra_utils;
|
||||
pub mod camera;
|
||||
pub mod colour;
|
||||
pub mod image;
|
||||
pub mod integrators;
|
||||
pub mod materials;
|
||||
pub mod math;
|
||||
pub mod mesh;
|
||||
pub mod random_distributions;
|
||||
pub mod raycasting;
|
||||
pub mod realtype;
|
||||
pub mod sampler;
|
||||
pub mod scene;
|
||||
pub mod util;
|
||||
|
||||
pub use camera::partial_render_scene;
|
||||
|
|
|
|||
268
src/main.rs
268
src/main.rs
|
|
@ -1,92 +1,35 @@
|
|||
use rayon::prelude::*;
|
||||
|
||||
use sdl2::event::Event;
|
||||
use sdl2::keyboard::Keycode;
|
||||
use sdl2::pixels::PixelFormatEnum;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::{Canvas, Texture};
|
||||
use sdl2::Sdl;
|
||||
|
||||
use clap::Arg;
|
||||
|
||||
use std::path::{Path, PathBuf};
|
||||
use std::sync::{mpsc, Arc};
|
||||
use std::time::Duration;
|
||||
|
||||
use vanrijn::accumulation_buffer::AccumulationBuffer;
|
||||
use vanrijn::colour::{ColourRgbF, NamedColour, Spectrum};
|
||||
use vanrijn::image::{ClampingToneMapper, ImageRgbU8};
|
||||
use vanrijn::materials::LambertianMaterial;
|
||||
use vanrijn::math::Vec3;
|
||||
use vanrijn::mesh::load_obj;
|
||||
use vanrijn::partial_render_scene;
|
||||
use vanrijn::raycasting::{BoundingVolumeHierarchy, Plane, Primitive, Sphere};
|
||||
use nalgebra::Vector3;
|
||||
|
||||
use std::cmp::min;
|
||||
use std::rc::Rc;
|
||||
|
||||
use vanrijn::camera::partial_render_scene;
|
||||
use vanrijn::colour::{ColourRgbF, NamedColour};
|
||||
use vanrijn::image::{ClampingToneMapper, ImageRgbF, ImageRgbU8, ToneMapper};
|
||||
use vanrijn::materials::{LambertianMaterial, PhongMaterial};
|
||||
use vanrijn::raycasting::{Plane, Sphere};
|
||||
use vanrijn::scene::Scene;
|
||||
use vanrijn::util::TileIterator;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct CommandLineParameters {
|
||||
width: usize,
|
||||
height: usize,
|
||||
output_file: Option<PathBuf>,
|
||||
time: f64,
|
||||
}
|
||||
|
||||
fn parse_args() -> CommandLineParameters {
|
||||
let matches = clap::App::new("vanrijn")
|
||||
.version("alpha")
|
||||
.author("Matthew Gordon <matthew@gordon.earth")
|
||||
.arg(
|
||||
Arg::with_name("size")
|
||||
.long("size")
|
||||
.value_name("SIZE")
|
||||
.help("The width and height of the output image, in pixels.")
|
||||
.takes_value(true)
|
||||
.number_of_values(2)
|
||||
.required(true),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("output_png")
|
||||
.long("out")
|
||||
.value_name("FILENAME")
|
||||
.help("Filename for output PNG.")
|
||||
.takes_value(true)
|
||||
.required(false),
|
||||
)
|
||||
.arg(
|
||||
Arg::with_name("time")
|
||||
.long("time")
|
||||
.value_name("SECONDS")
|
||||
.takes_value(true)
|
||||
.default_value("0"),
|
||||
)
|
||||
.get_matches();
|
||||
let mut size_iter = matches.values_of("size").unwrap();
|
||||
let width = size_iter.next().unwrap().parse().unwrap();
|
||||
let height = size_iter.next().unwrap().parse().unwrap();
|
||||
let output_file = matches.value_of_os("output_png").map(PathBuf::from);
|
||||
let time = matches.value_of("time").unwrap().parse().unwrap();
|
||||
CommandLineParameters {
|
||||
width,
|
||||
height,
|
||||
output_file,
|
||||
time,
|
||||
}
|
||||
}
|
||||
|
||||
fn update_texture(image: &ImageRgbU8, texture: &mut Texture) {
|
||||
texture
|
||||
.update(
|
||||
Rect::new(0, 0, image.get_width() as u32, image.get_height() as u32),
|
||||
image.get_pixel_data(),
|
||||
image.get_width() * ImageRgbU8::num_channels(),
|
||||
None,
|
||||
image.get_pixel_data().as_slice(),
|
||||
(image.get_width() * ImageRgbU8::num_channels()) as usize,
|
||||
)
|
||||
.expect("Couldn't update texture.");
|
||||
}
|
||||
|
||||
fn init_canvas(
|
||||
image_width: usize,
|
||||
image_height: usize,
|
||||
image_width: u32,
|
||||
image_height: u32,
|
||||
) -> Result<(Sdl, Canvas<sdl2::video::Window>), Box<dyn std::error::Error>> {
|
||||
let sdl_context = sdl2::init()?;
|
||||
let video_subsystem = sdl_context.video()?;
|
||||
|
|
@ -102,11 +45,8 @@ fn init_canvas(
|
|||
}
|
||||
|
||||
pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let parameters = parse_args();
|
||||
let image_width = parameters.width;
|
||||
let image_height = parameters.height;
|
||||
|
||||
let mut rendered_image = AccumulationBuffer::new(image_width, image_height);
|
||||
let image_width = 1200;
|
||||
let image_height = 900;
|
||||
|
||||
let (sdl_context, mut canvas) = init_canvas(image_width, image_height)?;
|
||||
|
||||
|
|
@ -116,118 +56,88 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
image_width as u32,
|
||||
image_height as u32,
|
||||
)?;
|
||||
|
||||
let model_file_path =
|
||||
Path::new(env!("CARGO_MANIFEST_DIR")).join("test_data/stanford_bunny.obj");
|
||||
println!("Loading object...");
|
||||
let mut model_object = load_obj(
|
||||
&model_file_path,
|
||||
Arc::new(LambertianMaterial {
|
||||
colour: Spectrum::reflection_from_linear_rgb(&ColourRgbF::from_named(
|
||||
NamedColour::Yellow,
|
||||
)),
|
||||
diffuse_strength: 0.05,
|
||||
//reflection_strength: 0.9,
|
||||
}),
|
||||
)?;
|
||||
println!("Building BVH...");
|
||||
let model_bvh: Box<dyn Primitive> =
|
||||
Box::new(BoundingVolumeHierarchy::build(model_object.as_mut_slice()));
|
||||
println!("Constructing Scene...");
|
||||
let mut output_image = ImageRgbF::<f64>::new(image_width, image_height);
|
||||
|
||||
let scene = Scene {
|
||||
camera_location: Vec3::new(-2.0, 1.0, -5.0),
|
||||
camera_location: Vector3::new(0.0, 0.0, 0.0),
|
||||
objects: vec![
|
||||
Box::new(vec![
|
||||
Box::new(Plane::new(
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
-2.0,
|
||||
Arc::new(LambertianMaterial {
|
||||
colour: Spectrum::reflection_from_linear_rgb(&ColourRgbF::new(
|
||||
0.55, 0.27, 0.04,
|
||||
)),
|
||||
diffuse_strength: 0.1,
|
||||
}),
|
||||
)) as Box<dyn Primitive>,
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(-6.25, -0.5, 1.0),
|
||||
1.0,
|
||||
Arc::new(LambertianMaterial {
|
||||
colour: Spectrum::reflection_from_linear_rgb(&ColourRgbF::from_named(
|
||||
NamedColour::Green,
|
||||
)),
|
||||
diffuse_strength: 0.1,
|
||||
}),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(-4.25, -0.5, 2.0),
|
||||
1.0,
|
||||
Arc::new(LambertianMaterial {
|
||||
colour: Spectrum::reflection_from_linear_rgb(&ColourRgbF::from_named(
|
||||
NamedColour::Blue,
|
||||
)),
|
||||
diffuse_strength: 0.1,
|
||||
// diffuse_strength: 0.01,
|
||||
// reflection_strength: 0.99,
|
||||
}),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vec3::new(-5.0, 1.5, 1.0),
|
||||
1.0,
|
||||
Arc::new(LambertianMaterial {
|
||||
colour: Spectrum::reflection_from_linear_rgb(&ColourRgbF::from_named(
|
||||
NamedColour::Red,
|
||||
)),
|
||||
diffuse_strength: 0.05,
|
||||
//smoothness: 100.0,
|
||||
//specular_strength: 1.0,
|
||||
}),
|
||||
)),
|
||||
]) as Box<dyn Primitive>,
|
||||
model_bvh,
|
||||
Box::new(Plane::new(
|
||||
Vector3::new(0.0, 1.0, 0.0),
|
||||
-2.0,
|
||||
Rc::new(LambertianMaterial {
|
||||
colour: ColourRgbF::new(0.55, 0.27, 0.04),
|
||||
diffuse_strength: 0.1,
|
||||
}),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vector3::new(1.25, -0.5, 6.0),
|
||||
1.0,
|
||||
Rc::new(LambertianMaterial {
|
||||
colour: ColourRgbF::from_named(NamedColour::Green),
|
||||
diffuse_strength: 0.1,
|
||||
}),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vector3::new(-1.25, -0.5, 6.0),
|
||||
1.0,
|
||||
Rc::new(LambertianMaterial {
|
||||
colour: ColourRgbF::from_named(NamedColour::Blue),
|
||||
diffuse_strength: 0.1,
|
||||
}),
|
||||
)),
|
||||
Box::new(Sphere::new(
|
||||
Vector3::new(0.0, 1.5, 6.0),
|
||||
1.0,
|
||||
Rc::new(PhongMaterial {
|
||||
colour: ColourRgbF::from_named(NamedColour::Red),
|
||||
diffuse_strength: 0.05,
|
||||
smoothness: 20.0,
|
||||
specular_strength: 250.0,
|
||||
}),
|
||||
)),
|
||||
],
|
||||
};
|
||||
println!("Done.");
|
||||
|
||||
let mut event_pump = sdl_context.event_pump()?;
|
||||
|
||||
let (tile_tx, tile_rx) = mpsc::channel();
|
||||
let mut tile_rx = Some(tile_rx);
|
||||
|
||||
let worker_boss = std::thread::spawn(move || {
|
||||
let end_tx = tile_tx.clone();
|
||||
TileIterator::new(image_width as usize, image_height as usize, 2048)
|
||||
.cycle()
|
||||
.map(move |tile| (tile, tile_tx.clone()))
|
||||
.par_bridge()
|
||||
.try_for_each(|(tile, tx)| {
|
||||
let rendered_tile = partial_render_scene(&scene, tile, image_height, image_width);
|
||||
|
||||
// There's nothing we can do if this fails, and we're already
|
||||
// at the end of the function anyway, so just ignore result.
|
||||
tx.send(Some((tile, rendered_tile))).ok()
|
||||
});
|
||||
end_tx.send(None).ok();
|
||||
});
|
||||
|
||||
let mut i = 0;
|
||||
'running: loop {
|
||||
if let Some(ref tile_rx) = tile_rx {
|
||||
for message in tile_rx.try_iter() {
|
||||
if let Some((tile, tile_accumulation_buffer)) = message {
|
||||
rendered_image.merge_tile(&tile, &tile_accumulation_buffer);
|
||||
let rgb_image = rendered_image.to_image_rgb_u8(&ClampingToneMapper {});
|
||||
update_texture(&rgb_image, &mut rendered_image_texture);
|
||||
canvas.copy(&rendered_image_texture, None, None).unwrap();
|
||||
canvas.present();
|
||||
} else if let Some(image_filename) = parameters.output_file {
|
||||
rendered_image
|
||||
.to_image_rgb_u8(&ClampingToneMapper {})
|
||||
.write_png(&image_filename)?;
|
||||
break 'running;
|
||||
let tile_size = 128;
|
||||
for tile_row in 0..1 + (output_image.get_height() + 1) / tile_size {
|
||||
for tile_column in 0..1 + (output_image.get_width() + 1) / tile_size {
|
||||
let row_start = tile_row * tile_size;
|
||||
let row_end = min(tile_row * tile_size + tile_size, output_image.get_height());
|
||||
let column_start = tile_column * tile_size;
|
||||
let column_end = min(
|
||||
tile_column * tile_size + tile_size,
|
||||
output_image.get_width(),
|
||||
);
|
||||
partial_render_scene(
|
||||
&mut output_image,
|
||||
&scene,
|
||||
row_start,
|
||||
row_end,
|
||||
column_start,
|
||||
column_end,
|
||||
);
|
||||
let mut output_image_rgbu8 = ImageRgbU8::new(image_width, image_height);
|
||||
ClampingToneMapper {}.apply_tone_mapping(&output_image, &mut output_image_rgbu8);
|
||||
update_texture(&output_image_rgbu8, &mut rendered_image_texture);
|
||||
canvas.copy(&rendered_image_texture, None, None)?;
|
||||
canvas.present();
|
||||
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
| Event::KeyDown {
|
||||
keycode: Some(Keycode::Escape),
|
||||
..
|
||||
} => break 'running,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
i = (i + 1) % 255;
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
|
|
@ -241,7 +151,5 @@ pub fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||
|
||||
::std::thread::sleep(Duration::new(0, 1_000_000_000u32 / 60));
|
||||
}
|
||||
drop(tile_rx.take());
|
||||
worker_boss.join().expect("Couldn't join worker threads.");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,66 @@
|
|||
use nalgebra::{RealField, Vector3};
|
||||
|
||||
use super::colour::{ColourRgbF, NamedColour};
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub trait Material<T: RealField>: Debug {
|
||||
fn bsdf<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Fn(Vector3<T>, Vector3<T>, ColourRgbF<T>) -> ColourRgbF<T> + 'a>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LambertianMaterial<T: RealField> {
|
||||
pub colour: ColourRgbF<T>,
|
||||
pub diffuse_strength: T,
|
||||
}
|
||||
|
||||
impl<T: RealField> LambertianMaterial<T> {
|
||||
pub fn new_dummy() -> LambertianMaterial<T> {
|
||||
LambertianMaterial {
|
||||
colour: ColourRgbF::new(T::one(), T::one(), T::one()),
|
||||
diffuse_strength: T::one(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RealField> Material<T> for LambertianMaterial<T> {
|
||||
fn bsdf<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Fn(Vector3<T>, Vector3<T>, ColourRgbF<T>) -> ColourRgbF<T> + 'a> {
|
||||
Box::new(
|
||||
move |_w_o: Vector3<T>, _w_i: Vector3<T>, colour_in: ColourRgbF<T>| {
|
||||
self.colour * colour_in * self.diffuse_strength
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhongMaterial<T: RealField> {
|
||||
pub colour: ColourRgbF<T>,
|
||||
pub diffuse_strength: T,
|
||||
pub specular_strength: T,
|
||||
pub smoothness: T,
|
||||
}
|
||||
|
||||
impl<T: RealField> Material<T> for PhongMaterial<T> {
|
||||
fn bsdf<'a>(
|
||||
&'a self,
|
||||
) -> Box<dyn Fn(Vector3<T>, Vector3<T>, ColourRgbF<T>) -> ColourRgbF<T> + 'a> {
|
||||
Box::new(
|
||||
move |w_o: Vector3<T>, w_i: Vector3<T>, colour_in: ColourRgbF<T>| {
|
||||
if w_i.z < T::zero() || w_o.z < T::zero() {
|
||||
ColourRgbF::from_vector3(&Vector3::zeros())
|
||||
} else {
|
||||
let reflection_vector = Vector3::new(-w_i.x, -w_i.y, w_i.z);
|
||||
self.colour * colour_in * self.diffuse_strength
|
||||
+ ColourRgbF::from_named(NamedColour::White)
|
||||
* w_o.dot(&reflection_vector).abs().powf(self.smoothness)
|
||||
* (self.specular_strength / w_i.dot(&Vector3::z_axis()))
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
use crate::colour::{Photon, Spectrum};
|
||||
use crate::math::Vec3;
|
||||
|
||||
use super::{Material, MaterialSampleResult};
|
||||
|
||||
use rand::distributions::Open01;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use std::f64::consts::PI;
|
||||
use std::fmt::Debug;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct LambertianMaterial {
|
||||
pub colour: Spectrum,
|
||||
pub diffuse_strength: f64,
|
||||
}
|
||||
|
||||
impl LambertianMaterial {
|
||||
pub fn new_dummy() -> LambertianMaterial {
|
||||
LambertianMaterial {
|
||||
colour: Spectrum::black(),
|
||||
diffuse_strength: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for LambertianMaterial {
|
||||
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3<f64>, &Vec3<f64>, &Photon) -> Photon + 'a> {
|
||||
Box::new(move |_w_o: &Vec3<f64>, _w_i: &Vec3<f64>, photon_in: &Photon| {
|
||||
let mut result = self.colour.scale_photon(photon_in);
|
||||
result.intensity *= self.diffuse_strength;
|
||||
result
|
||||
})
|
||||
}
|
||||
|
||||
fn sample(&self, _w_i: &Vec3<f64>, _photon: &Photon) -> MaterialSampleResult {
|
||||
let mut rng = thread_rng();
|
||||
let mut w_o = Vec3::new(
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
0.0,
|
||||
);
|
||||
while w_o.norm_squared() > 1.0 {
|
||||
w_o = Vec3::new(
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
0.0,
|
||||
);
|
||||
}
|
||||
w_o.coords[2] = (1.0 - w_o.x() * w_o.x() - w_o.y() * w_o.y())
|
||||
.sqrt()
|
||||
.max(0.0);
|
||||
let cos_theta = w_o.dot(&Vec3::unit_z());
|
||||
let sin_theta = (1.0 - cos_theta * cos_theta).sqrt();
|
||||
MaterialSampleResult {
|
||||
direction: w_o.normalize(),
|
||||
pdf: (cos_theta * sin_theta) / PI,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,36 +0,0 @@
|
|||
use crate::math::Vec3;
|
||||
|
||||
use super::colour::Photon;
|
||||
use super::random_distributions::{CosineWeightedHemisphere, RandomDistribution};
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
pub mod lambertian_material;
|
||||
pub use lambertian_material::LambertianMaterial;
|
||||
|
||||
pub mod phong_material;
|
||||
pub use phong_material::PhongMaterial;
|
||||
|
||||
pub mod reflective_material;
|
||||
pub use reflective_material::ReflectiveMaterial;
|
||||
|
||||
pub mod smooth_transparent_dialectric;
|
||||
pub use smooth_transparent_dialectric::SmoothTransparentDialectric;
|
||||
|
||||
pub struct MaterialSampleResult {
|
||||
pub direction: Vec3<f64>,
|
||||
pub pdf: f64,
|
||||
}
|
||||
|
||||
type BsdfFunc<'a> = Box<dyn Fn(&Vec3<f64>, &Vec3<f64>, &Photon) -> Photon + 'a>;
|
||||
|
||||
pub trait Material: Debug + Sync + Send {
|
||||
fn bsdf<'a>(&'a self) -> BsdfFunc<'a>;
|
||||
|
||||
fn sample(&self, _w_i: &Vec3<f64>, _photon: &Photon) -> MaterialSampleResult {
|
||||
let distribution = CosineWeightedHemisphere::new();
|
||||
let direction = distribution.value();
|
||||
let pdf = distribution.pdf(direction);
|
||||
MaterialSampleResult { direction, pdf }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,37 +0,0 @@
|
|||
use crate::colour::{Photon, Spectrum};
|
||||
use crate::math::Vec3;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::Material;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PhongMaterial {
|
||||
pub colour: Spectrum,
|
||||
pub diffuse_strength: f64,
|
||||
pub specular_strength: f64,
|
||||
pub smoothness: f64,
|
||||
}
|
||||
|
||||
impl Material for PhongMaterial {
|
||||
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3<f64>, &Vec3<f64>, &Photon) -> Photon + 'a> {
|
||||
Box::new(move |w_o: &Vec3<f64>, w_i: &Vec3<f64>, photon_in: &Photon| {
|
||||
if w_i.z() < 0.0 || w_o.z() < 0.0 {
|
||||
Photon {
|
||||
wavelength: photon_in.wavelength,
|
||||
intensity: 0.0,
|
||||
}
|
||||
} else {
|
||||
let reflection_vector = Vec3::new(-w_i.x(), -w_i.y(), w_i.z());
|
||||
let intensity = self.colour.scale_photon(photon_in).intensity
|
||||
* self.diffuse_strength
|
||||
+ w_o.dot(&reflection_vector).abs().powf(self.smoothness)
|
||||
* (self.specular_strength / w_i.dot(&Vec3::unit_z()));
|
||||
Photon {
|
||||
wavelength: photon_in.wavelength,
|
||||
intensity,
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +0,0 @@
|
|||
use crate::colour::{Photon, Spectrum};
|
||||
use crate::math::Vec3;
|
||||
|
||||
use std::fmt::Debug;
|
||||
|
||||
use super::{Material, MaterialSampleResult};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct ReflectiveMaterial {
|
||||
pub colour: Spectrum,
|
||||
pub diffuse_strength: f64,
|
||||
pub reflection_strength: f64,
|
||||
}
|
||||
|
||||
impl Material for ReflectiveMaterial {
|
||||
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3<f64>, &Vec3<f64>, &Photon) -> Photon + 'a> {
|
||||
Box::new(move |w_o: &Vec3<f64>, w_i: &Vec3<f64>, photon_in: &Photon| {
|
||||
if w_i.z() <= 0.0 || w_o.z() <= 0.0 {
|
||||
Photon {
|
||||
wavelength: photon_in.wavelength,
|
||||
intensity: 0.0,
|
||||
}
|
||||
} else {
|
||||
let reflection_vector = Vec3::new(-w_o.x(), -w_o.y(), w_o.z());
|
||||
let mut photon_out = self.colour.scale_photon(photon_in);
|
||||
photon_out.intensity *= self.diffuse_strength;
|
||||
let sigma = 0.05;
|
||||
let two = 2.0;
|
||||
// These are normalized vectors, but sometimes rounding errors cause the
|
||||
// dot product to be slightly above 1 or below 0. The call to clamp
|
||||
// ensures the values stay within the domain of acos,
|
||||
let theta = w_i.dot(&reflection_vector).clamp(0.0, 1.0).abs().acos();
|
||||
let reflection_factor =
|
||||
self.reflection_strength * (-(theta.powf(two)) / (two * sigma * sigma)).exp();
|
||||
photon_out.intensity =
|
||||
photon_out.intensity * (1.0 - reflection_factor) + reflection_factor;
|
||||
photon_out
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn sample(&self, w_o: &Vec3<f64>, _photon: &Photon) -> MaterialSampleResult {
|
||||
MaterialSampleResult {
|
||||
direction: Vec3::new(-w_o.x(), -w_o.y(), w_o.z()),
|
||||
pdf: 1.0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,84 +0,0 @@
|
|||
use super::{Bsdf, Material};
|
||||
use crate::colour::ColourRgbF;
|
||||
use crate::math::Vec3;
|
||||
use crate::realtype::NormalizedToU32;
|
||||
|
||||
use std::error::Error;
|
||||
use std::fs::File;
|
||||
use std::io::BufReader;
|
||||
use std::sync::Arc;
|
||||
|
||||
use std::f64::consts::{FRAC_PI_2, PI};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RgbSampledBsdfMaterial {
|
||||
lut: Arc<Vec<Vec<Vec<Vec<Vec3>>>>>,
|
||||
}
|
||||
|
||||
fn expand_and_index<T: Clone>(v: &mut Vec<T>, i: usize, default: T) -> &mut T {
|
||||
if v.len() < i + 1 {
|
||||
v.resize(i + 1, default);
|
||||
}
|
||||
&mut v[i]
|
||||
}
|
||||
|
||||
impl RgbSampledBsdfMaterial {
|
||||
pub fn from_csv_file(filename: &str) -> Result<RgbSampledBsdfMaterial, Box<dyn Error>> {
|
||||
let csv_file = File::open(filename)?;
|
||||
let mut reader = csv::Reader::from_reader(BufReader::new(&csv_file));
|
||||
let mut lut = Vec::new();
|
||||
for row_result in reader.records() {
|
||||
let row = row_result?;
|
||||
let theta_in_index = row[0].trim().parse::<usize>()?;
|
||||
let phi_in_index = row[1].trim().parse::<usize>()?;
|
||||
let theta_out_index = row[2].trim().parse::<usize>()?;
|
||||
let phi_out_index = row[3].trim().parse::<usize>()?;
|
||||
let red = row[4].trim().parse::<f64>()?;
|
||||
let green = row[5].trim().parse::<f64>()?;
|
||||
let blue = row[6].trim().parse::<f64>()?;
|
||||
*expand_and_index(
|
||||
expand_and_index(
|
||||
expand_and_index(
|
||||
expand_and_index(&mut lut, theta_in_index, Vec::new()),
|
||||
phi_in_index,
|
||||
Vec::new(),
|
||||
),
|
||||
theta_out_index,
|
||||
Vec::new(),
|
||||
),
|
||||
phi_out_index,
|
||||
Vec3::zeros(),
|
||||
) = Vec3::new(red, green, blue);
|
||||
}
|
||||
let lut = Arc::new(lut);
|
||||
Ok(RgbSampledBsdfMaterial { lut })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Material for RgbSampledBsdfMaterial {
|
||||
fn bsdf(&self) -> Bsdf {
|
||||
let lut = Arc::clone(&self.lut);
|
||||
Box::new(move |w_in, w_out, colour_in| {
|
||||
if w_in.z() < 0.0 || w_out.z() < 0.0 {
|
||||
return ColourRgbF::new(0.0, 0.0, 0.0);
|
||||
}
|
||||
let theta_in = w_in.z().acos();
|
||||
let theta_in_index = (theta_in / FRAC_PI_2).normalized_to_u32(4) as usize;
|
||||
let phi_in = w_in.y().atan2(w_in.x()) + PI;
|
||||
let phi_in_index = (phi_in / (2.0 * PI)).normalized_to_u32(6) as usize;
|
||||
let theta_out = w_out.z().acos();
|
||||
let theta_out_index = (theta_out / FRAC_PI_2).normalized_to_u32(4) as usize;
|
||||
let phi_out = w_out.y().atan2(w_out.x()) + PI;
|
||||
let phi_out_index = (phi_out / (2.0 * PI)).normalized_to_u32(6) as usize;
|
||||
ColourRgbF::from_vec3(
|
||||
&colour_in.as_vec3().component_mul(
|
||||
&lut[theta_in_index][phi_in_index][theta_out_index][phi_out_index],
|
||||
),
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
fn sample(&self, w_o: &Vec3) -> Vec<Vec3> {
|
||||
vec![Vec3::new(-w_o.x(), -w_o.y(), w_o.z())]
|
||||
}
|
||||
}
|
||||
|
|
@ -1,115 +0,0 @@
|
|||
use crate::colour::{Photon, Spectrum};
|
||||
use crate::materials::{Material, MaterialSampleResult};
|
||||
use crate::math::Vec3;
|
||||
|
||||
use rand::random;
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FresnelResult {
|
||||
reflection_direction: Vec3<f64>,
|
||||
reflection_strength: f64,
|
||||
transmission_direction: Vec3<f64>,
|
||||
transmission_strength: f64,
|
||||
}
|
||||
|
||||
fn fresnel(w_i: &Vec3<f64>, eta1: f64, eta2: f64) -> FresnelResult {
|
||||
let normal = if w_i.z() > 0.0 {
|
||||
Vec3::unit_z()
|
||||
} else {
|
||||
-Vec3::unit_z()
|
||||
};
|
||||
let reflection_direction = Vec3::new(-w_i.x(), -w_i.y(), w_i.z());
|
||||
let r = eta1 / eta2;
|
||||
let cos_theta1 = normal.dot(w_i);
|
||||
let cos_theta2_squared = 1.0 - r * r * (1.0 - cos_theta1 * cos_theta1);
|
||||
let mut result = if cos_theta2_squared >= 0.0 {
|
||||
let cos_theta2 = cos_theta2_squared.sqrt();
|
||||
let reflection_strength_parallel_sqrt =
|
||||
(eta1 * cos_theta2 - eta2 * cos_theta1) / (eta1 * cos_theta2 + eta2 * cos_theta1);
|
||||
let reflection_strength_perpendicular_sqrt =
|
||||
(eta1 * cos_theta1 - eta2 * cos_theta2) / (eta1 * cos_theta1 + eta2 * cos_theta2);
|
||||
let reflection_strength = 0.5
|
||||
* (reflection_strength_parallel_sqrt * reflection_strength_parallel_sqrt
|
||||
+ reflection_strength_perpendicular_sqrt * reflection_strength_perpendicular_sqrt);
|
||||
let transmission_direction =
|
||||
(-r * w_i + (r * cos_theta1 - cos_theta2) * normal).normalize();
|
||||
let transmission_strength = 1.0 - reflection_strength;
|
||||
FresnelResult {
|
||||
reflection_direction,
|
||||
reflection_strength,
|
||||
transmission_direction,
|
||||
transmission_strength,
|
||||
}
|
||||
} else {
|
||||
let reflection_strength = 1.0;
|
||||
let transmission_strength = 0.0;
|
||||
let transmission_direction = Default::default();
|
||||
FresnelResult {
|
||||
reflection_direction,
|
||||
reflection_strength,
|
||||
transmission_direction,
|
||||
transmission_strength,
|
||||
}
|
||||
};
|
||||
if w_i.z() < 0.0 {
|
||||
result.reflection_direction.coords[2] *= -1.0;
|
||||
result.transmission_direction.coords[2] *= -1.0;
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct SmoothTransparentDialectric {
|
||||
eta: Spectrum,
|
||||
}
|
||||
|
||||
impl SmoothTransparentDialectric {
|
||||
pub fn new(eta: Spectrum) -> SmoothTransparentDialectric {
|
||||
SmoothTransparentDialectric { eta }
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for SmoothTransparentDialectric {
|
||||
fn bsdf<'a>(&'a self) -> Box<dyn Fn(&Vec3<f64>, &Vec3<f64>, &Photon) -> Photon + 'a> {
|
||||
Box::new(move |w_o: &Vec3<f64>, w_i: &Vec3<f64>, photon_in: &Photon| {
|
||||
let (eta1, eta2) = if w_i.z() >= 0.0 {
|
||||
(1.0, self.eta.intensity_at_wavelength(photon_in.wavelength))
|
||||
} else {
|
||||
(self.eta.intensity_at_wavelength(photon_in.wavelength), 1.0)
|
||||
};
|
||||
let fresnel = fresnel(w_i, eta1, eta2);
|
||||
if (*w_o - fresnel.reflection_direction).norm_squared() < 0.0000000001 {
|
||||
photon_in.scale_intensity(fresnel.reflection_strength)
|
||||
} else if (*w_o - fresnel.transmission_direction).norm_squared() < 0.0000000001 {
|
||||
photon_in.scale_intensity(fresnel.transmission_strength)
|
||||
} else {
|
||||
photon_in.set_intensity(0.0)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn sample(&self, w_i: &Vec3<f64>, photon: &Photon) -> MaterialSampleResult {
|
||||
let (eta1, eta2) = if w_i.z() >= 0.0 {
|
||||
(1.0, self.eta.intensity_at_wavelength(photon.wavelength))
|
||||
} else {
|
||||
(self.eta.intensity_at_wavelength(photon.wavelength), 1.0)
|
||||
};
|
||||
let fresnel = fresnel(w_i, eta1, eta2);
|
||||
if fresnel.transmission_strength <= 0.0000000001 {
|
||||
MaterialSampleResult {
|
||||
direction: fresnel.reflection_direction,
|
||||
pdf: 0.5,
|
||||
}
|
||||
} else if fresnel.reflection_strength <= 0.0000000001 || random() {
|
||||
MaterialSampleResult {
|
||||
direction: fresnel.transmission_direction,
|
||||
pdf: 0.5,
|
||||
}
|
||||
} else {
|
||||
MaterialSampleResult {
|
||||
direction: fresnel.reflection_direction,
|
||||
pdf: 0.5,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,160 +0,0 @@
|
|||
use super::{Float, Mat3, Mat4, Vec3, Vec4};
|
||||
use std::ops::{Mul, MulAssign};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Affine3<T: Float> {
|
||||
matrix: Mat4<T>,
|
||||
}
|
||||
|
||||
impl<T: Float> Affine3<T> {
|
||||
pub fn translation(delta: Vec3<T>) -> Self {
|
||||
#[rustfmt::skip]
|
||||
let matrix = Mat4::new(T::one(), T::zero(), T::zero(), delta.x(),
|
||||
T::zero(), T::one() , T::zero(), delta.y(),
|
||||
T::zero(), T::zero(), T::one(), delta.z(),
|
||||
T::zero(), T::zero(), T::zero(), T::one());
|
||||
Self { matrix }
|
||||
}
|
||||
|
||||
pub fn rotation(axis: Vec3<T>, angle: T) -> Self {
|
||||
let x = axis.x();
|
||||
let y = axis.y();
|
||||
let z = axis.z();
|
||||
let cos = angle.cos();
|
||||
let ncos = T::one() - cos;
|
||||
let sin = angle.sin();
|
||||
#[rustfmt::skip]
|
||||
let matrix = Mat4::new(x*x*ncos+cos, y*x*ncos-z*sin, z*x*ncos+y*sin, T::zero(),
|
||||
x*y*ncos+z*sin, y*y*ncos+cos, z*y*ncos-x*sin, T::zero(),
|
||||
x*z*ncos-y*sin, y*z*ncos+x*sin, z*z*ncos+cos, T::zero(),
|
||||
T::zero(), T::zero(), T::zero(), T::one());
|
||||
Self { matrix }
|
||||
}
|
||||
|
||||
pub fn scale(s: T) -> Self {
|
||||
#[rustfmt::skip]
|
||||
let matrix = Mat4::new(s, T::zero(), T::zero(), T::zero(),
|
||||
T::zero(), s , T::zero(), T::zero(),
|
||||
T::zero(), T::zero(), s, T::zero(),
|
||||
T::zero(), T::zero(), T::zero(), T::one());
|
||||
Self { matrix }
|
||||
}
|
||||
|
||||
pub fn get_element(&self, row: usize, column: usize) -> T {
|
||||
self.matrix.get_element(row, column)
|
||||
}
|
||||
|
||||
pub fn get_row(&self, row: usize) -> Vec4<T> {
|
||||
self.matrix.get_row(row)
|
||||
}
|
||||
|
||||
pub fn get_column(&self, column: usize) -> Vec4<T> {
|
||||
self.matrix.get_column(column)
|
||||
}
|
||||
|
||||
pub fn linear_map(&self) -> Mat3<T> {
|
||||
Mat3::new(
|
||||
self.matrix.get_element(0, 0),
|
||||
self.matrix.get_element(0, 1),
|
||||
self.matrix.get_element(0, 2),
|
||||
self.matrix.get_element(1, 0),
|
||||
self.matrix.get_element(1, 1),
|
||||
self.matrix.get_element(1, 2),
|
||||
self.matrix.get_element(2, 0),
|
||||
self.matrix.get_element(2, 1),
|
||||
self.matrix.get_element(2, 2),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn inverse(&self) -> Affine3<T> {
|
||||
// linear map should always be invertable.
|
||||
let inner = self.linear_map().try_inverse().unwrap();
|
||||
let translation = inner * self.matrix.get_column(3).xyz();
|
||||
#[rustfmt::skip]
|
||||
let matrix = Mat4::new(
|
||||
inner.get_element(0,0), inner.get_element(0,1), inner.get_element(0,2), translation.x(),
|
||||
inner.get_element(1,0), inner.get_element(1,1), inner.get_element(1,2), translation.y(),
|
||||
inner.get_element(2,0), inner.get_element(2,1), inner.get_element(2,2), translation.z(),
|
||||
T::zero(), T::zero(), T::zero(), T::one());
|
||||
Self { matrix }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<Affine3<T>> for Affine3<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Affine3<T>) -> Affine3<T> {
|
||||
let matrix = self.matrix * rhs.matrix;
|
||||
Affine3 { matrix }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<Mat4<T>> for Affine3<T> {
|
||||
type Output = Mat4<T>;
|
||||
|
||||
fn mul(self, rhs: Mat4<T>) -> Mat4<T> {
|
||||
self.matrix * rhs
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<Affine3<T>> for Mat4<T> {
|
||||
type Output = Mat4<T>;
|
||||
|
||||
fn mul(self, rhs: Affine3<T>) -> Mat4<T> {
|
||||
self * rhs.matrix
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> MulAssign<Affine3<T>> for Affine3<T> {
|
||||
fn mul_assign(&mut self, rhs: Affine3<T>) {
|
||||
self.matrix *= rhs.matrix
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<Vec4<T>> for Affine3<T> {
|
||||
type Output = Vec4<T>;
|
||||
|
||||
fn mul(self, rhs: Vec4<T>) -> Vec4<T> {
|
||||
self.matrix * rhs
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn translate_translates_vector() {
|
||||
let p = Vec4::new(1.0, 2.0, 3.0, 1.0);
|
||||
let v = Vec3::new(4.0, 5.0, 6.0);
|
||||
let target = Affine3::translation(v);
|
||||
let diff = (target * p).xyz() - (p.xyz() + v);
|
||||
assert!(diff.norm() < 0.0000000001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rotate_rotates_vector() {
|
||||
let x = Vec4::new(1.0, 0.0, 0.0, 1.0);
|
||||
let y = Vec4::new(0.0, 1.0, 0.0, 1.0);
|
||||
let z = Vec4::new(0.0, 0.0, 1.0, 1.0);
|
||||
let target = Affine3::rotation(z.xyz(), std::f64::consts::PI/2.0) * y;
|
||||
let diff = -x.xyz() - target.xyz();
|
||||
assert!(diff.norm() < 0.0000000001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn linear_map_is_inner_matrix() {
|
||||
#[rustfmt::skip]
|
||||
let target = Affine3{
|
||||
matrix: Mat4::new(1.0, 2.0, 3.0, 4.0,
|
||||
5.0, 6.0, 7.0, 8.0,
|
||||
9.0, 10.0, 11.0, 12.0,
|
||||
0.0, 0.0, 0.0, 1.0)};
|
||||
let linear_map = target.linear_map();
|
||||
for i in 0..2 {
|
||||
for j in 0..2 {
|
||||
assert!(linear_map.get_element(i, j) == target.get_element(i, j));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,35 +0,0 @@
|
|||
use super::Float;
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct Mat2<T: Float> {
|
||||
pub elements: [[T; 2]; 2],
|
||||
}
|
||||
|
||||
impl<T: Float> Mat2<T> {
|
||||
pub fn new(m00: T, m01: T, m10: T, m11: T) -> Mat2<T> {
|
||||
Mat2 {
|
||||
elements: [[m00, m01], [m10, m11]],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn determinant(&self) -> T {
|
||||
self.elements[0][0] * self.elements[1][1] - self.elements[0][1] * self.elements[1][0]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn determinant_returns_expected_value() {
|
||||
let target1 = Mat2::new(1.0, 2.0, 3.0, 4.0);
|
||||
let target2 = Mat2::new(1.0, -2.0, 3.0, 4.0);
|
||||
let target3 = Mat2::new(1.0, 1.0, 1.0, 1.0);
|
||||
let target4 = Mat2::new(21.0, 45.0, -16.0, 0.0);
|
||||
assert!(target1.determinant() == -2.0);
|
||||
assert!(target2.determinant() == 10.0);
|
||||
assert!(target3.determinant() == 0.0);
|
||||
assert!(target4.determinant() == 720.0);
|
||||
}
|
||||
}
|
||||
351
src/math/mat3.rs
351
src/math/mat3.rs
|
|
@ -1,351 +0,0 @@
|
|||
use super::{Float, Mat2, Vec3};
|
||||
|
||||
use std::ops::{Mul, MulAssign};
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct Mat3<T: Float> {
|
||||
elements: [[T; 3]; 3],
|
||||
}
|
||||
|
||||
impl<T: Float> Mat3<T> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(m00: T, m01: T, m02: T, m10: T, m11: T, m12: T, m20: T, m21: T, m22: T) -> Mat3<T> {
|
||||
Mat3 {
|
||||
elements: [[m00, m01, m02], [m10, m11, m12], [m20, m21, m22]],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn identity() -> Mat3<T> {
|
||||
Mat3 {
|
||||
elements: [
|
||||
[T::one(), T::zero(), T::zero()],
|
||||
[T::zero(), T::one(), T::zero()],
|
||||
[T::zero(), T::zero(), T::one()],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rows(r0: &Vec3<T>, r1: &Vec3<T>, r2: &Vec3<T>) -> Mat3<T> {
|
||||
let mut elements = [[T::zero(); 3]; 3];
|
||||
for (row, v) in elements.iter_mut().zip([r0, r1, r2].iter()) {
|
||||
for (it, val) in row.iter_mut().zip(v.coords.iter()) {
|
||||
*it = *val;
|
||||
}
|
||||
}
|
||||
Mat3 { elements }
|
||||
}
|
||||
|
||||
pub fn get_element(&self, row: usize, column: usize) -> T {
|
||||
self.elements[row][column]
|
||||
}
|
||||
|
||||
pub fn get_row(&self, row: usize) -> Vec3<T> {
|
||||
Vec3 {
|
||||
coords: self.elements[row],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column(&self, column: usize) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (coord, row) in coords.iter_mut().zip(self.elements.iter()) {
|
||||
*coord = row[column];
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
|
||||
pub fn transpose(&self) -> Mat3<T> {
|
||||
let mut elements = [[T::zero(); 3]; 3];
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
elements[i][j] = self.elements[j][i];
|
||||
}
|
||||
}
|
||||
Mat3 { elements }
|
||||
}
|
||||
|
||||
pub fn first_minor(&self, row: usize, column: usize) -> T {
|
||||
let mut elements = [[T::zero(); 2]; 2];
|
||||
let mut i_dst = 0;
|
||||
let mut j_dst = 0;
|
||||
for i_src in 0..3 {
|
||||
if i_src != row {
|
||||
for j_src in 0..3 {
|
||||
if j_src != column {
|
||||
elements[i_dst][j_dst] = self.get_element(i_src, j_src);
|
||||
j_dst += 1;
|
||||
}
|
||||
}
|
||||
i_dst += 1;
|
||||
j_dst = 0;
|
||||
}
|
||||
}
|
||||
let minor_matrix = Mat2 { elements };
|
||||
minor_matrix.determinant()
|
||||
}
|
||||
|
||||
pub fn cofactor(&self, row: usize, column: usize) -> T {
|
||||
T::from((-1i32).pow((row + column) as u32)) * self.first_minor(row, column)
|
||||
}
|
||||
|
||||
pub fn cofactor_matrix(&self) -> Mat3<T> {
|
||||
let mut elements = [[T::zero(); 3]; 3];
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
elements[i][j] = self.cofactor(i, j);
|
||||
}
|
||||
}
|
||||
Mat3 { elements }
|
||||
}
|
||||
|
||||
pub fn determinant(&self) -> T {
|
||||
self.elements[0][0] * self.first_minor(0, 0) - self.elements[0][1] * self.first_minor(0, 1)
|
||||
+ self.elements[0][2] * self.first_minor(0, 2)
|
||||
}
|
||||
|
||||
pub fn try_inverse(&self) -> Option<Mat3<T>> {
|
||||
let determinant = self.determinant();
|
||||
if determinant == T::zero() {
|
||||
None
|
||||
} else {
|
||||
Some(self.cofactor_matrix().transpose() * determinant)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<Mat3<T>> for Mat3<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
let mut elements = [[T::zero(); 3]; 3];
|
||||
for row in 0..3 {
|
||||
for column in 0..3 {
|
||||
elements[row][column] = self.get_row(row).dot(&rhs.get_column(column));
|
||||
}
|
||||
}
|
||||
Mat3 { elements }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> MulAssign<Mat3<T>> for Mat3<T> {
|
||||
fn mul_assign(&mut self, rhs: Self) {
|
||||
for row in 0..3 {
|
||||
let mut new_row = [T::zero(); 3];
|
||||
for column in 0..3 {
|
||||
new_row[column] = self.get_row(row).dot(&rhs.get_column(column));
|
||||
}
|
||||
self.elements[row] = new_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<Vec3<T>> for Mat3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn mul(self, rhs: Vec3<T>) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (coord, row) in coords.iter_mut().zip(self.elements.iter()) {
|
||||
*coord = Vec3 { coords: *row }.dot(&rhs);
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<&Vec3<T>> for Mat3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn mul(self, rhs: &Vec3<T>) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (coord, row) in coords.iter_mut().zip(self.elements.iter()) {
|
||||
*coord = Vec3 { coords: *row }.dot(rhs);
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<T> for Mat3<T> {
|
||||
type Output = Mat3<T>;
|
||||
|
||||
fn mul(self, rhs: T) -> Mat3<T> {
|
||||
let mut elements = [[T::zero(); 3]; 3];
|
||||
for i in 0..3 {
|
||||
for j in 0..3 {
|
||||
elements[i][j] = self.elements[i][j] * rhs;
|
||||
}
|
||||
}
|
||||
Mat3 { elements }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn elements_are_in_expected_locations() {
|
||||
let target = Mat3::new(1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0);
|
||||
assert!(target.get_element(0, 0) == 1.0);
|
||||
assert!(target.get_element(0, 1) == 2.0);
|
||||
assert!(target.get_element(0, 2) == 3.0);
|
||||
assert!(target.get_element(1, 0) == 4.0);
|
||||
assert!(target.get_element(1, 1) == 5.0);
|
||||
assert!(target.get_element(1, 2) == 6.0);
|
||||
assert!(target.get_element(2, 0) == 7.0);
|
||||
assert!(target.get_element(2, 1) == 8.0);
|
||||
assert!(target.get_element(2, 2) == 9.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_rows_places_values_in_rows() {
|
||||
let target = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
assert!(target.get_element(0, 0) == 1.0);
|
||||
assert!(target.get_element(0, 1) == 2.0);
|
||||
assert!(target.get_element(0, 2) == 3.0);
|
||||
assert!(target.get_element(1, 0) == 4.0);
|
||||
assert!(target.get_element(1, 1) == 5.0);
|
||||
assert!(target.get_element(1, 2) == 6.0);
|
||||
assert!(target.get_element(2, 0) == 7.0);
|
||||
assert!(target.get_element(2, 1) == 8.0);
|
||||
assert!(target.get_element(2, 2) == 9.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_column_returns_expected_value() {
|
||||
let target = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
assert!(target.get_column(0) == Vec3::new(1.0, 4.0, 7.0));
|
||||
assert!(target.get_column(1) == Vec3::new(2.0, 5.0, 8.0));
|
||||
assert!(target.get_column(2) == Vec3::new(3.0, 6.0, 9.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn transpose_returns_expected_result() {
|
||||
let target = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let expected = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 4.0, 7.0),
|
||||
&Vec3::new(2.0, 5.0, 8.0),
|
||||
&Vec3::new(3.0, 6.0, 9.0),
|
||||
);
|
||||
assert!(target.transpose() == expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cofactor_matrix_returns_expected_result() {
|
||||
let target = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let expected = Mat3::from_rows(
|
||||
&Vec3::new(-3.0, 6.0, -3.0),
|
||||
&Vec3::new(6.0, -12.0, 6.0),
|
||||
&Vec3::new(-3.0, 6.0, -3.0),
|
||||
);
|
||||
assert!(target.cofactor_matrix() == expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn determinant_returns_expected_result() {
|
||||
let target = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 3.0, 2.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
assert!(target.determinant() == 9.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverse_of_singular_matrix_is_none_result() {
|
||||
let target = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let expected = None;
|
||||
assert!(target.try_inverse() == expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverse_of_identity_is_identity() {
|
||||
assert!(Mat3::<f64>::identity().try_inverse() == Some(Mat3::identity()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn inverse_returns_expected_result() {
|
||||
let target = Mat3::from_rows(
|
||||
&Vec3::new(4.0, -5.0, -2.0),
|
||||
&Vec3::new(5.0, -6.0, -2.0),
|
||||
&Vec3::new(-8.0, 9.0, 3.0),
|
||||
);
|
||||
let expected = Some(Mat3::from_rows(
|
||||
&Vec3::new(0.0, -3.0, -2.0),
|
||||
&Vec3::new(1.0, -4.0, -2.0),
|
||||
&Vec3::new(-3.0, 4.0, 1.0),
|
||||
));
|
||||
assert!(target.try_inverse() == expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_with_mat3_returns_expected_result() {
|
||||
let a = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let b = Mat3::from_rows(
|
||||
&Vec3::new(10.0, 11.0, 12.0),
|
||||
&Vec3::new(13.0, 14.0, 15.0),
|
||||
&Vec3::new(16.0, 17.0, 18.0),
|
||||
);
|
||||
let c = Mat3::from_rows(
|
||||
&Vec3::new(84.0, 90.0, 96.0),
|
||||
&Vec3::new(201.0, 216.0, 231.0),
|
||||
&Vec3::new(318.0, 342.0, 366.0),
|
||||
);
|
||||
assert!(a * b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_assign_returns_expected_result() {
|
||||
let mut a = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let b = Mat3::from_rows(
|
||||
&Vec3::new(10.0, 11.0, 12.0),
|
||||
&Vec3::new(13.0, 14.0, 15.0),
|
||||
&Vec3::new(16.0, 17.0, 18.0),
|
||||
);
|
||||
let c = Mat3::from_rows(
|
||||
&Vec3::new(84.0, 90.0, 96.0),
|
||||
&Vec3::new(201.0, 216.0, 231.0),
|
||||
&Vec3::new(318.0, 342.0, 366.0),
|
||||
);
|
||||
|
||||
a *= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_with_vec3_returns_expected_result() {
|
||||
let a = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let b = Vec3::new(10.0, 11.0, 12.0);
|
||||
let c = Vec3::new(68.0, 167.0, 266.0);
|
||||
assert!(a * b == c);
|
||||
}
|
||||
}
|
||||
234
src/math/mat4.rs
234
src/math/mat4.rs
|
|
@ -1,234 +0,0 @@
|
|||
use super::{Float,Vec4};
|
||||
|
||||
use std::ops::{Mul, MulAssign};
|
||||
|
||||
#[derive(PartialEq, Debug)]
|
||||
pub struct Mat4<T:Float> {
|
||||
elements: [[T; 4]; 4],
|
||||
}
|
||||
|
||||
impl<T:Float> Mat4<T> {
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub fn new(
|
||||
m00: T,
|
||||
m01: T,
|
||||
m02: T,
|
||||
m03: T,
|
||||
m10: T,
|
||||
m11: T,
|
||||
m12: T,
|
||||
m13: T,
|
||||
m20: T,
|
||||
m21: T,
|
||||
m22: T,
|
||||
m23: T,
|
||||
m30: T,
|
||||
m31: T,
|
||||
m32: T,
|
||||
m33: T,
|
||||
) -> Mat4<T> {
|
||||
Mat4 {
|
||||
elements: [
|
||||
[m00, m01, m02, m03],
|
||||
[m10, m11, m12, m13],
|
||||
[m20, m21, m22, m23],
|
||||
[m30, m31, m32, m33],
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_rows(r0: &Vec4<T>, r1: &Vec4<T>, r2: &Vec4<T>, r3: &Vec4<T>) -> Mat4<T> {
|
||||
let mut elements = [[T::zero(); 4]; 4];
|
||||
for (row, v) in elements.iter_mut().zip([r0, r1, r2, r3].iter()) {
|
||||
for (it, val) in row.iter_mut().zip(v.coords.iter()) {
|
||||
*it = *val;
|
||||
}
|
||||
}
|
||||
Mat4 { elements }
|
||||
}
|
||||
|
||||
pub fn get_element(&self, row: usize, column: usize) -> T {
|
||||
self.elements[row][column]
|
||||
}
|
||||
|
||||
pub fn get_row(&self, row: usize) -> Vec4<T> {
|
||||
Vec4 {
|
||||
coords: self.elements[row],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_column(&self, column: usize) -> Vec4<T> {
|
||||
let mut coords = [T::zero(); 4];
|
||||
for (coord, row) in coords.iter_mut().zip(self.elements.iter()) {
|
||||
*coord = row[column];
|
||||
}
|
||||
Vec4 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Float> Mul<Mat4<T>> for Mat4<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Self) -> Self {
|
||||
let mut elements = [[T::zero(); 4]; 4];
|
||||
for row in 0..4 {
|
||||
for column in 0..4 {
|
||||
elements[row][column] = self.get_row(row).dot(&rhs.get_column(column));
|
||||
}
|
||||
}
|
||||
Mat4 { elements }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Float> MulAssign<Mat4<T>> for Mat4<T> {
|
||||
fn mul_assign(&mut self, rhs: Self) {
|
||||
for row in 0..4 {
|
||||
let mut new_row = [T::zero(); 4];
|
||||
for column in 0..4 {
|
||||
new_row[column] = self.get_row(row).dot(&rhs.get_column(column));
|
||||
}
|
||||
self.elements[row] = new_row;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Float> Mul<Vec4<T>> for Mat4<T> {
|
||||
type Output = Vec4<T>;
|
||||
|
||||
fn mul(self, rhs: Vec4<T>) -> Vec4<T> {
|
||||
let mut coords = [T::zero(); 4];
|
||||
for (coord, row) in coords.iter_mut().zip(self.elements.iter()) {
|
||||
*coord = Vec4 { coords: *row }.dot(&rhs);
|
||||
}
|
||||
Vec4 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn elements_are_in_expected_locations() {
|
||||
let target = Mat4::new(
|
||||
1.0, 2.0, 3.0, 4.0, 5.0, 6.0, 7.0, 8.0, 9.0, 10.0, 11.0, 12.0, 13.0, 14.0, 15.0, 16.0,
|
||||
);
|
||||
assert!(target.get_element(0, 0) == 1.0);
|
||||
assert!(target.get_element(0, 1) == 2.0);
|
||||
assert!(target.get_element(0, 2) == 3.0);
|
||||
assert!(target.get_element(0, 3) == 4.0);
|
||||
assert!(target.get_element(1, 0) == 5.0);
|
||||
assert!(target.get_element(1, 1) == 6.0);
|
||||
assert!(target.get_element(1, 2) == 7.0);
|
||||
assert!(target.get_element(1, 3) == 8.0);
|
||||
assert!(target.get_element(2, 0) == 9.0);
|
||||
assert!(target.get_element(2, 1) == 10.0);
|
||||
assert!(target.get_element(2, 2) == 11.0);
|
||||
assert!(target.get_element(2, 3) == 12.0);
|
||||
assert!(target.get_element(3, 0) == 13.0);
|
||||
assert!(target.get_element(3, 1) == 14.0);
|
||||
assert!(target.get_element(3, 2) == 15.0);
|
||||
assert!(target.get_element(3, 3) == 16.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_rows_places_values_in_rows() {
|
||||
let target = Mat4::from_rows(
|
||||
&Vec4::new(1.0, 2.0, 3.0, 4.0),
|
||||
&Vec4::new(5.0, 6.0, 7.0, 8.0),
|
||||
&Vec4::new(9.0, 10.0, 11.0, 12.0),
|
||||
&Vec4::new(13.0, 14.0, 15.0, 16.0),
|
||||
);
|
||||
assert!(target.get_element(0, 0) == 1.0);
|
||||
assert!(target.get_element(0, 1) == 2.0);
|
||||
assert!(target.get_element(0, 2) == 3.0);
|
||||
assert!(target.get_element(0, 3) == 4.0);
|
||||
assert!(target.get_element(1, 0) == 5.0);
|
||||
assert!(target.get_element(1, 1) == 6.0);
|
||||
assert!(target.get_element(1, 2) == 7.0);
|
||||
assert!(target.get_element(1, 3) == 8.0);
|
||||
assert!(target.get_element(2, 0) == 9.0);
|
||||
assert!(target.get_element(2, 1) == 10.0);
|
||||
assert!(target.get_element(2, 2) == 11.0);
|
||||
assert!(target.get_element(2, 3) == 12.0);
|
||||
assert!(target.get_element(3, 0) == 13.0);
|
||||
assert!(target.get_element(3, 1) == 14.0);
|
||||
assert!(target.get_element(3, 2) == 15.0);
|
||||
assert!(target.get_element(3, 3) == 16.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_column_returns_expected_value() {
|
||||
let target = Mat4::from_rows(
|
||||
&Vec4::new(1.0, 2.0, 3.0, 4.0),
|
||||
&Vec4::new(5.0, 6.0, 7.0, 8.0),
|
||||
&Vec4::new(9.0, 10.0, 11.0, 12.0),
|
||||
&Vec4::new(13.0, 14.0, 15.0, 16.0),
|
||||
);
|
||||
assert!(target.get_column(0) == Vec4::new(1.0, 5.0, 9.0, 13.0));
|
||||
assert!(target.get_column(1) == Vec4::new(2.0, 6.0, 10.0, 14.0));
|
||||
assert!(target.get_column(2) == Vec4::new(3.0, 7.0, 11.0, 15.0));
|
||||
assert!(target.get_column(3) == Vec4::new(4.0, 8.0, 12.0, 16.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_with_mat4_returns_expected_result() {
|
||||
let a = Mat4::from_rows(
|
||||
&Vec4::new(1.0, 2.0, 3.0, 4.0),
|
||||
&Vec4::new(5.0, 6.0, 7.0, 8.0),
|
||||
&Vec4::new(9.0, 10.0, 11.0, 12.0),
|
||||
&Vec4::new(13.0, 14.0, 15.0, 16.0),
|
||||
);
|
||||
let b = Mat4::from_rows(
|
||||
&Vec4::new(17.0, 18.0, 19.0, 20.0),
|
||||
&Vec4::new(21.0, 22.0, 23.0, 24.0),
|
||||
&Vec4::new(25.0, 26.0, 27.0, 28.0),
|
||||
&Vec4::new(29.0, 30.0, 31.0, 32.0),
|
||||
);
|
||||
let c = Mat4::from_rows(
|
||||
&Vec4::new(250.0, 260.0, 270.0, 280.0),
|
||||
&Vec4::new(618.0, 644.0, 670.0, 696.0),
|
||||
&Vec4::new(986.0, 1028.0, 1070.0, 1112.0),
|
||||
&Vec4::new(1354.0, 1412.0, 1470.0, 1528.0),
|
||||
);
|
||||
assert!(a * b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_assign_returns_expected_result() {
|
||||
let mut a = Mat4::from_rows(
|
||||
&Vec4::new(1.0, 2.0, 3.0, 4.0),
|
||||
&Vec4::new(5.0, 6.0, 7.0, 8.0),
|
||||
&Vec4::new(9.0, 10.0, 11.0, 12.0),
|
||||
&Vec4::new(13.0, 14.0, 15.0, 16.0),
|
||||
);
|
||||
let b = Mat4::from_rows(
|
||||
&Vec4::new(17.0, 18.0, 19.0, 20.0),
|
||||
&Vec4::new(21.0, 22.0, 23.0, 24.0),
|
||||
&Vec4::new(25.0, 26.0, 27.0, 28.0),
|
||||
&Vec4::new(29.0, 30.0, 31.0, 32.0),
|
||||
);
|
||||
let c = Mat4::from_rows(
|
||||
&Vec4::new(250.0, 260.0, 270.0, 280.0),
|
||||
&Vec4::new(618.0, 644.0, 670.0, 696.0),
|
||||
&Vec4::new(986.0, 1028.0, 1070.0, 1112.0),
|
||||
&Vec4::new(1354.0, 1412.0, 1470.0, 1528.0),
|
||||
);
|
||||
|
||||
a *= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_with_vec4_returns_expected_result() {
|
||||
let a = Mat4::from_rows(
|
||||
&Vec4::new(1.0, 2.0, 3.0, 4.0),
|
||||
&Vec4::new(5.0, 6.0, 7.0, 8.0),
|
||||
&Vec4::new(9.0, 10.0, 11.0, 12.0),
|
||||
&Vec4::new(13.0, 14.0, 15.0, 16.0),
|
||||
);
|
||||
let b = Vec4::new(17.0, 18.0, 19.0, 20.0);
|
||||
let c = Vec4::new(190.0, 486.0, 782.0, 1078.0);
|
||||
assert!(a * b == c);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,23 +0,0 @@
|
|||
mod number;
|
||||
pub use number::*;
|
||||
|
||||
mod vec2;
|
||||
pub use vec2::*;
|
||||
|
||||
mod vec3;
|
||||
pub use vec3::*;
|
||||
|
||||
mod vec4;
|
||||
pub use vec4::*;
|
||||
|
||||
mod mat2;
|
||||
pub use mat2::*;
|
||||
|
||||
mod mat3;
|
||||
pub use mat3::*;
|
||||
|
||||
mod mat4;
|
||||
pub use mat4::*;
|
||||
|
||||
mod affine3;
|
||||
pub use affine3::*;
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
use std::{
|
||||
cmp::PartialOrd,
|
||||
iter::Sum,
|
||||
ops::{Add, AddAssign, Div, Mul, MulAssign, Neg, Sub, SubAssign},
|
||||
};
|
||||
|
||||
pub trait HasZero {
|
||||
fn zero() -> Self;
|
||||
}
|
||||
|
||||
pub trait HasOne {
|
||||
fn one() -> Self;
|
||||
}
|
||||
|
||||
pub trait Float:
|
||||
Copy
|
||||
+ HasZero
|
||||
+ HasOne
|
||||
+ Add<Output = Self>
|
||||
+ AddAssign
|
||||
+ Div<Output = Self>
|
||||
+ Mul<Output = Self>
|
||||
+ MulAssign
|
||||
+ Sub<Output = Self>
|
||||
+ SubAssign
|
||||
+ Neg<Output = Self>
|
||||
+ Sum
|
||||
+ PartialOrd
|
||||
+ From<f32>
|
||||
+ From<f64>
|
||||
+ From<u8>
|
||||
+ From<i8>
|
||||
+ From<u16>
|
||||
+ From<i16>
|
||||
+ From<u32>
|
||||
+ From<i32>
|
||||
{
|
||||
fn abs(self) -> Self;
|
||||
fn sqrt(self) -> Self;
|
||||
fn sin(self) -> Self;
|
||||
fn cos(self) -> Self;
|
||||
}
|
||||
|
||||
impl HasZero for f64 {
|
||||
fn zero() -> Self {
|
||||
0.0
|
||||
}
|
||||
}
|
||||
|
||||
impl HasOne for f64 {
|
||||
fn one() -> Self {
|
||||
1.0
|
||||
}
|
||||
}
|
||||
|
||||
impl Float for f64 {
|
||||
fn abs(self) -> Self {
|
||||
self.abs()
|
||||
}
|
||||
|
||||
fn sqrt(self) -> Self {
|
||||
self.sqrt()
|
||||
}
|
||||
|
||||
fn sin(self) -> Self {
|
||||
self.sin()
|
||||
}
|
||||
|
||||
fn cos(self) -> Self {
|
||||
self.cos()
|
||||
}
|
||||
}
|
||||
187
src/math/vec2.rs
187
src/math/vec2.rs
|
|
@ -1,187 +0,0 @@
|
|||
use super::Float;
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
|
||||
|
||||
#[derive(PartialEq, Debug, Copy, Clone)]
|
||||
pub struct Vec2<T: Float> {
|
||||
coords: [T; 2],
|
||||
}
|
||||
|
||||
impl<T: Float> Vec2<T> {
|
||||
pub fn new(x: T, y: T) -> Self {
|
||||
Vec2 { coords: [x, y] }
|
||||
}
|
||||
|
||||
pub fn x(&self) -> T {
|
||||
self.coords[0]
|
||||
}
|
||||
|
||||
pub fn y(&self) -> T {
|
||||
self.coords[1]
|
||||
}
|
||||
|
||||
pub fn dot(&self, rhs: &Vec2<T>) -> T {
|
||||
self.coords
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(rhs.coords.iter().copied())
|
||||
.map(|(a_elem, b_elem)| a_elem * b_elem)
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub fn perp(&self, rhs: &Vec2<T>) -> T {
|
||||
self.x() * rhs.y() - self.y() * rhs.x()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Add for Vec2<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
let mut coords = [T::zero(); 2];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a + b;
|
||||
}
|
||||
Vec2 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> AddAssign for Vec2<T> {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
for (a, b) in self.coords.iter_mut().zip(rhs.coords.iter().copied()) {
|
||||
*a += b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Sub for Vec2<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
let mut coords = [T::zero(); 2];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a - b;
|
||||
}
|
||||
Vec2 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> SubAssign for Vec2<T> {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
for (a, b) in self.coords.iter_mut().zip(rhs.coords.iter().copied()) {
|
||||
*a -= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<T> for Vec2<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: T) -> Vec2<T> {
|
||||
let mut coords = [T::zero(); 2];
|
||||
for (r, a) in coords.iter_mut().zip(self.coords.iter().copied()) {
|
||||
*r = a * rhs;
|
||||
}
|
||||
Vec2 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> MulAssign<T> for Vec2<T> {
|
||||
fn mul_assign(&mut self, rhs: T) {
|
||||
for a in self.coords.iter_mut() {
|
||||
*a *= rhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quickcheck::{Arbitrary, Gen};
|
||||
|
||||
impl Arbitrary for Vec2<f64> {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Vec2<f64> {
|
||||
Vec2::new(f64::arbitrary(g), f64::arbitrary(g))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn x_returns_first_element() {
|
||||
let target = Vec2::new(1.0, 2.0);
|
||||
assert!(target.x() == 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn y_returns_second_element() {
|
||||
let target = Vec2::new(1.0, 2.0);
|
||||
assert!(target.y() == 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_product_returns_correct_result() {
|
||||
let a = Vec2::new(1.0, 2.0);
|
||||
let b = Vec2::new(4.0, 5.0);
|
||||
assert!(a.dot(&b) == 14.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_returns_correct_result() {
|
||||
let a = Vec2::new(1.0, 2.0);
|
||||
let b = Vec2::new(4.0, 5.0);
|
||||
let c = Vec2::new(5.0, 7.0);
|
||||
assert!(a + b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_assign_returns_correct_result() {
|
||||
let mut a = Vec2::new(1.0, 2.0);
|
||||
let b = Vec2::new(4.0, 5.0);
|
||||
let c = Vec2::new(5.0, 7.0);
|
||||
a += b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_returns_correct_result() {
|
||||
let a = Vec2::new(1.0, 2.0);
|
||||
let b = Vec2::new(4.0, 6.0);
|
||||
let c = Vec2::new(-3.0, -4.0);
|
||||
assert!(a - b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_assign_returns_correct_result() {
|
||||
let mut a = Vec2::new(1.0, 2.0);
|
||||
let b = Vec2::new(4.0, 6.0);
|
||||
let c = Vec2::new(-3.0, -4.0);
|
||||
a -= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_by_scalar_returns_correct_result() {
|
||||
let a = Vec2::new(1.0, 2.0);
|
||||
let b = 0.5;
|
||||
let c = Vec2::new(0.5, 1.0);
|
||||
assert!(a * b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_assign_by_scalar_returns_correct_result() {
|
||||
let mut a = Vec2::new(1.0, 2.0);
|
||||
let b = 0.5;
|
||||
let c = Vec2::new(0.5, 1.0);
|
||||
a *= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
}
|
||||
537
src/math/vec3.rs
537
src/math/vec3.rs
|
|
@ -1,537 +0,0 @@
|
|||
use super::{Float, Mat3};
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
use std::ops::{Add, AddAssign, Div, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign};
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Debug, Default)]
|
||||
pub struct Vec3<T: Float> {
|
||||
pub coords: [T; 3],
|
||||
}
|
||||
|
||||
impl<T: Float> Vec3<T> {
|
||||
pub fn new(x: T, y: T, z: T) -> Self {
|
||||
Vec3 { coords: [x, y, z] }
|
||||
}
|
||||
|
||||
pub fn from_slice(v: &[T]) -> Self {
|
||||
let mut coords = [T::zero(); 3];
|
||||
coords.clone_from_slice(v);
|
||||
Vec3 { coords }
|
||||
}
|
||||
|
||||
pub fn zeros() -> Vec3<T> {
|
||||
Vec3 {
|
||||
coords: [T::zero(), T::zero(), T::zero()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unit_x() -> Vec3<T> {
|
||||
Vec3 {
|
||||
coords: [T::one(), T::zero(), T::zero()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unit_y() -> Vec3<T> {
|
||||
Vec3 {
|
||||
coords: [T::zero(), T::one(), T::zero()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn unit_z() -> Vec3<T> {
|
||||
Vec3 {
|
||||
coords: [T::zero(), T::zero(), T::one()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn x(&self) -> T {
|
||||
self.coords[0]
|
||||
}
|
||||
|
||||
pub fn y(&self) -> T {
|
||||
self.coords[1]
|
||||
}
|
||||
|
||||
pub fn z(&self) -> T {
|
||||
self.coords[2]
|
||||
}
|
||||
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
&self.coords
|
||||
}
|
||||
|
||||
pub fn dot(&self, rhs: &Vec3<T>) -> T {
|
||||
self.coords
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(rhs.coords.iter().copied())
|
||||
.map(|(a_elem, b_elem)| a_elem * b_elem)
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub fn cross(&self, rhs: &Vec3<T>) -> Vec3<T> {
|
||||
let x = self.y() * rhs.z() - self.z() * rhs.y();
|
||||
let y = self.z() * rhs.x() - self.x() * rhs.z();
|
||||
let z = self.x() * rhs.y() - self.y() * rhs.x();
|
||||
Vec3 { coords: [x, y, z] }
|
||||
}
|
||||
|
||||
pub fn abs(&self) -> Self {
|
||||
Vec3::new(self.x().abs(), self.y().abs(), self.z().abs())
|
||||
}
|
||||
|
||||
pub fn norm_squared(&self) -> T {
|
||||
self.dot(self)
|
||||
}
|
||||
|
||||
pub fn norm(&self) -> T {
|
||||
self.norm_squared().sqrt()
|
||||
}
|
||||
|
||||
pub fn normalize(&self) -> Self {
|
||||
let mut coords = [T::zero(); 3];
|
||||
let inverse_norm = T::one() / self.norm();
|
||||
for (r, a) in coords.iter_mut().zip(self.coords.iter().copied()) {
|
||||
*r = a * inverse_norm;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
|
||||
pub fn smallest_coord(&self) -> usize {
|
||||
let x = self.x().abs();
|
||||
let y = self.y().abs();
|
||||
let z = self.z().abs();
|
||||
if x < y {
|
||||
if x < z {
|
||||
0
|
||||
} else {
|
||||
2
|
||||
}
|
||||
} else if y < z {
|
||||
1
|
||||
} else {
|
||||
2
|
||||
}
|
||||
}
|
||||
|
||||
pub fn component_mul(&self, rhs: &Self) -> Self {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (elem, lhs_elem, rhs_elem) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*elem = lhs_elem * rhs_elem;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Index<usize> for Vec3<T> {
|
||||
type Output = T;
|
||||
|
||||
fn index(&self, i: usize) -> &T {
|
||||
&self.coords[i]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> IndexMut<usize> for Vec3<T> {
|
||||
fn index_mut(&mut self, i: usize) -> &mut T {
|
||||
&mut self.coords[i]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Add<Vec3<T>> for &Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn add(self, rhs: Vec3<T>) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a + b;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Add<&Vec3<T>> for &Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn add(self, rhs: &Vec3<T>) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a + b;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Add for Vec3<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a + b;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> AddAssign for Vec3<T> {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
for (a, b) in self.coords.iter_mut().zip(rhs.coords.iter().copied()) {
|
||||
*a += b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Neg for Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
fn neg(self) -> Vec3<T> {
|
||||
Vec3::new(-self.x(), -self.y(), -self.z())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Sub for &Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a - b;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Sub for Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn sub(self, rhs: Self) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a - b;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> SubAssign for Vec3<T> {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
for (a, b) in self.coords.iter_mut().zip(rhs.coords.iter().copied()) {
|
||||
*a -= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<T> for &Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn mul(self, rhs: T) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a) in coords.iter_mut().zip(self.coords.iter().copied()) {
|
||||
*r = a * rhs;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<T> for Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn mul(self, rhs: T) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a) in coords.iter_mut().zip(self.coords.iter().copied()) {
|
||||
*r = a * rhs;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> MulAssign<T> for Vec3<T> {
|
||||
fn mul_assign(&mut self, rhs: T) {
|
||||
for a in self.coords.iter_mut() {
|
||||
*a *= rhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Float> Mul<Mat3<T>> for &Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn mul(self, rhs: Mat3<T>) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for i in 0..3 {
|
||||
coords[i] = self.dot(&rhs.get_column(i));
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Float> Mul<Mat3<T>> for Vec3<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: Mat3<T>) -> Self {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for i in 0..3 {
|
||||
coords[i] = self.dot(&rhs.get_column(i));
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T:Float> MulAssign<Mat3<T>> for Vec3<T> {
|
||||
fn mul_assign(&mut self, rhs: Mat3<T>) {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for i in 0..3 {
|
||||
coords[i] = self.dot(&rhs.get_column(i));
|
||||
}
|
||||
self.coords = coords;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3<f64>> for f64 {
|
||||
type Output = Vec3<f64>;
|
||||
|
||||
fn mul(self, rhs: Vec3<f64>) -> Vec3<f64> {
|
||||
rhs * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<&Vec3<f64>> for f64 {
|
||||
type Output = Vec3<f64>;
|
||||
|
||||
fn mul(self, rhs: &Vec3<f64>) -> Vec3<f64> {
|
||||
rhs * self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Div<T> for &Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn div(self, rhs: T) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a) in coords.iter_mut().zip(self.coords.iter().copied()) {
|
||||
*r = a / rhs;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Div<T> for Vec3<T> {
|
||||
type Output = Vec3<T>;
|
||||
|
||||
fn div(self, rhs: T) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
for (r, a) in coords.iter_mut().zip(self.coords.iter().copied()) {
|
||||
*r = a / rhs;
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use quickcheck::{Arbitrary, Gen};
|
||||
|
||||
impl<T: Arbitrary + Float> Arbitrary for Vec3<T> {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Vec3<T> {
|
||||
Vec3::new(T::arbitrary(g), T::arbitrary(g), T::arbitrary(g))
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn x_returns_first_element() {
|
||||
let target = Vec3::new(1.0, 2.0, 3.0);
|
||||
assert!(target.x() == 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn y_returns_second_element() {
|
||||
let target = Vec3::new(1.0, 2.0, 3.0);
|
||||
assert!(target.y() == 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn z_returns_third_element() {
|
||||
let target = Vec3::new(1.0, 2.0, 3.0);
|
||||
assert!(target.z() == 3.0);
|
||||
}
|
||||
|
||||
/*#[test]
|
||||
fn from_iterator_takes_first_three_elements() {
|
||||
let target = Vec3::from_iterator([1.0, 2.0, 3.0].iter());
|
||||
assert!(target = Vec3::new(1.0, 2.0, 3.0));
|
||||
}*/
|
||||
|
||||
#[test]
|
||||
fn dot_product_returns_correct_result() {
|
||||
let a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = Vec3::new(4.0, 5.0, 6.0);
|
||||
assert!(a.dot(&b) == 32.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cross_product_returns_correct_result() {
|
||||
let a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = Vec3::new(4.0, 5.0, 6.0);
|
||||
let c = Vec3::new(-3.0, 6.0, -3.0);
|
||||
assert!(a.cross(&b) == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn norm_returns_expected_value() {
|
||||
let target = Vec3::new(2.0, 3.0, 6.0);
|
||||
assert!(target.norm() == 7.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalized_vector_times_norm_yields_original() {
|
||||
let mut target = Vec3::new(2.0, 3.0, 6.0);
|
||||
let norm = target.norm();
|
||||
target = target.normalize();
|
||||
target *= norm;
|
||||
assert!(target == Vec3::new(2.0, 3.0, 6.0));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smallest_coord_works_for_x_when_positive() {
|
||||
let target = Vec3::new(1.0, 2.0, 3.0);
|
||||
assert!(target.smallest_coord() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smallest_coord_works_for_x_when_negative() {
|
||||
let target = Vec3::new(-2.0, -3.0, 3.0);
|
||||
assert!(target.smallest_coord() == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smallest_coord_works_for_y_when_positive() {
|
||||
let target = Vec3::new(2.0, 1.0, 3.0);
|
||||
assert!(target.smallest_coord() == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smallest_coord_works_for_y_when_negative() {
|
||||
let target = Vec3::new(-3.0, -2.0, 3.0);
|
||||
assert!(target.smallest_coord() == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smallest_coord_works_for_z_when_positive() {
|
||||
let target = Vec3::new(3.0, 2.0, 1.0);
|
||||
assert!(target.smallest_coord() == 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn smallest_coord_works_for_z_when_negative() {
|
||||
let target = Vec3::new(3.0, -3.0, -2.0);
|
||||
assert!(target.smallest_coord() == 2);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_returns_correct_result() {
|
||||
let a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = Vec3::new(4.0, 5.0, 6.0);
|
||||
let c = Vec3::new(5.0, 7.0, 9.0);
|
||||
assert!(a + b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_assign_returns_correct_result() {
|
||||
let mut a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = Vec3::new(4.0, 5.0, 6.0);
|
||||
let c = Vec3::new(5.0, 7.0, 9.0);
|
||||
a += b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_returns_correct_result() {
|
||||
let a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = Vec3::new(4.0, 6.0, 8.0);
|
||||
let c = Vec3::new(-3.0, -4.0, -5.0);
|
||||
assert!(a - b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_assign_returns_correct_result() {
|
||||
let mut a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = Vec3::new(4.0, 6.0, 8.0);
|
||||
let c = Vec3::new(-3.0, -4.0, -5.0);
|
||||
a -= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_by_scalar_returns_correct_result() {
|
||||
let a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = 0.5;
|
||||
let c = Vec3::new(0.5, 1.0, 1.5);
|
||||
assert!(a * b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn div_by_scalar_returns_correct_result() {
|
||||
let a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = 2.0;
|
||||
let c = Vec3::new(0.5, 1.0, 1.5);
|
||||
assert!(dbg!(a / b) == dbg!(c));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_assign_by_scalar_returns_correct_result() {
|
||||
let mut a = Vec3::new(1.0, 2.0, 3.0);
|
||||
let b = 0.5;
|
||||
let c = Vec3::new(0.5, 1.0, 1.5);
|
||||
a *= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_with_mat3_returns_expected_result() {
|
||||
let a = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let b = Vec3::new(10.0, 11.0, 12.0);
|
||||
let c = Vec3::new(138.0, 171.0, 204.0);
|
||||
assert!(b * a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_assign_with_mat3_returns_expected_result() {
|
||||
let a = Mat3::from_rows(
|
||||
&Vec3::new(1.0, 2.0, 3.0),
|
||||
&Vec3::new(4.0, 5.0, 6.0),
|
||||
&Vec3::new(7.0, 8.0, 9.0),
|
||||
);
|
||||
let mut b = Vec3::new(10.0, 11.0, 12.0);
|
||||
let c = Vec3::new(138.0, 171.0, 204.0);
|
||||
b *= a;
|
||||
assert!(b == c);
|
||||
}
|
||||
}
|
||||
212
src/math/vec4.rs
212
src/math/vec4.rs
|
|
@ -1,212 +0,0 @@
|
|||
use super::{Float, Vec3};
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
use std::ops::{Add, AddAssign, Mul, MulAssign, Sub, SubAssign};
|
||||
|
||||
#[derive(PartialEq, Debug, Clone, Copy)]
|
||||
pub struct Vec4<T: Float> {
|
||||
pub coords: [T; 4],
|
||||
}
|
||||
|
||||
impl<T: Float> Vec4<T> {
|
||||
pub fn new(x: T, y: T, z: T, w: T) -> Self {
|
||||
Vec4 {
|
||||
coords: [x, y, z, w],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn x(&self) -> T {
|
||||
self.coords[0]
|
||||
}
|
||||
|
||||
pub fn y(&self) -> T {
|
||||
self.coords[1]
|
||||
}
|
||||
|
||||
pub fn z(&self) -> T {
|
||||
self.coords[2]
|
||||
}
|
||||
|
||||
pub fn w(&self) -> T {
|
||||
self.coords[3]
|
||||
}
|
||||
|
||||
pub fn xyz(&self) -> Vec3<T> {
|
||||
let mut coords = [T::zero(); 3];
|
||||
coords.copy_from_slice(&self.coords[0..3]);
|
||||
Vec3 { coords }
|
||||
}
|
||||
|
||||
pub fn dot(&self, rhs: &Vec4<T>) -> T {
|
||||
self.coords
|
||||
.iter()
|
||||
.copied()
|
||||
.zip(rhs.coords.iter().copied())
|
||||
.map(|(a_elem, b_elem)| a_elem * b_elem)
|
||||
.sum()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Add for Vec4<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn add(self, rhs: Self) -> Self {
|
||||
let mut coords = [T::zero(); 4];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a + b;
|
||||
}
|
||||
Vec4 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> AddAssign for Vec4<T> {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
for (a, b) in self.coords.iter_mut().zip(rhs.coords.iter().copied()) {
|
||||
*a += b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Sub for Vec4<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn sub(self, rhs: Self) -> Self {
|
||||
let mut coords = [T::zero(); 4];
|
||||
for (r, a, b) in izip!(
|
||||
coords.iter_mut(),
|
||||
self.coords.iter().copied(),
|
||||
rhs.coords.iter().copied()
|
||||
) {
|
||||
*r = a - b;
|
||||
}
|
||||
Vec4 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> SubAssign for Vec4<T> {
|
||||
fn sub_assign(&mut self, rhs: Self) {
|
||||
for (a, b) in self.coords.iter_mut().zip(rhs.coords.iter().copied()) {
|
||||
*a -= b;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> Mul<T> for Vec4<T> {
|
||||
type Output = Self;
|
||||
|
||||
fn mul(self, rhs: T) -> Vec4<T> {
|
||||
let mut coords = [T::zero(); 4];
|
||||
for (r, a) in coords.iter_mut().zip(self.coords.iter().copied()) {
|
||||
*r = a * rhs;
|
||||
}
|
||||
Vec4 { coords }
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Float> MulAssign<T> for Vec4<T> {
|
||||
fn mul_assign(&mut self, rhs: T) {
|
||||
for a in self.coords.iter_mut() {
|
||||
*a *= rhs;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn x_returns_first_element() {
|
||||
let target = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
assert!(target.x() == 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn y_returns_second_element() {
|
||||
let target = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
assert!(target.y() == 2.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn z_returns_third_element() {
|
||||
let target = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
assert!(target.z() == 3.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn w_returns_third_element() {
|
||||
let target = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
assert!(target.w() == 4.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn xyz_returns_expected_value() {
|
||||
let target = Vec4::new(1.0, 2.0, 3.0, 4.0).xyz();
|
||||
assert!(target.x() == 1.0);
|
||||
assert!(target.y() == 2.0);
|
||||
assert!(target.z() == 3.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dot_product_returns_correct_result() {
|
||||
let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
let b = Vec4::new(4.0, 5.0, 6.0, 7.0);
|
||||
assert!(a.dot(&b) == 60.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_returns_correct_result() {
|
||||
let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
let b = Vec4::new(4.0, 5.0, 6.0, 7.0);
|
||||
let c = Vec4::new(5.0, 7.0, 9.0, 11.0);
|
||||
assert!(a + b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_assign_returns_correct_result() {
|
||||
let mut a = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
let b = Vec4::new(4.0, 5.0, 6.0, 7.0);
|
||||
let c = Vec4::new(5.0, 7.0, 9.0, 11.0);
|
||||
a += b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_returns_correct_result() {
|
||||
let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
let b = Vec4::new(4.0, 6.0, 8.0, 10.0);
|
||||
let c = Vec4::new(-3.0, -4.0, -5.0, -6.0);
|
||||
assert!(a - b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sub_assign_returns_correct_result() {
|
||||
let mut a = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
let b = Vec4::new(4.0, 6.0, 8.0, 10.0);
|
||||
let c = Vec4::new(-3.0, -4.0, -5.0, -6.0);
|
||||
a -= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_by_scalar_returns_correct_result() {
|
||||
let a = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
let b = 0.5;
|
||||
let c = Vec4::new(0.5, 1.0, 1.5, 2.0);
|
||||
assert!(a * b == c);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mul_assign_by_scalar_returns_correct_result() {
|
||||
let mut a = Vec4::new(1.0, 2.0, 3.0, 4.0);
|
||||
let b = 0.5;
|
||||
let c = Vec4::new(0.5, 1.0, 1.5, 2.0);
|
||||
a *= b;
|
||||
assert!(a == c);
|
||||
}
|
||||
}
|
||||
91
src/mesh.rs
91
src/mesh.rs
|
|
@ -1,91 +0,0 @@
|
|||
/// Load a model from a Wavefront .obj file
|
||||
mod wavefront_obj {
|
||||
use crate::materials::Material;
|
||||
use crate::math::Vec3;
|
||||
use crate::raycasting::{Primitive, Triangle};
|
||||
|
||||
use obj::{IndexTuple, Obj, SimplePolygon};
|
||||
|
||||
use std::io::Result;
|
||||
use std::path::Path;
|
||||
use std::sync::Arc;
|
||||
|
||||
fn get_vertex_and_normal(
|
||||
index_tuple: &IndexTuple,
|
||||
vertex_positions: &[[f32; 3]],
|
||||
normal_positions: &[[f32; 3]],
|
||||
) -> (Vec3<f64>, Vec3<f64>) {
|
||||
let &IndexTuple(vertex_index, _, maybe_normal_index) = index_tuple;
|
||||
(
|
||||
{
|
||||
let vertex_coords = &vertex_positions[vertex_index];
|
||||
Vec3::new(
|
||||
vertex_coords[0] as f64,
|
||||
vertex_coords[1] as f64,
|
||||
vertex_coords[2] as f64,
|
||||
)
|
||||
},
|
||||
match maybe_normal_index {
|
||||
Some(normal_index) => {
|
||||
let normal_coords = &normal_positions[normal_index];
|
||||
Vec3::new(
|
||||
normal_coords[0] as f64,
|
||||
normal_coords[1] as f64,
|
||||
normal_coords[2] as f64,
|
||||
)
|
||||
}
|
||||
None => Vec3::zeros(),
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
fn get_triangles(
|
||||
polygon: &SimplePolygon,
|
||||
vertex_positions: &[[f32; 3]],
|
||||
normal_positions: &[[f32; 3]],
|
||||
material: Arc<dyn Material>,
|
||||
) -> Vec<Triangle> {
|
||||
if let Some(v0_index) = polygon.iter().next() {
|
||||
let (v0_vertex, v0_normal) =
|
||||
get_vertex_and_normal(v0_index, vertex_positions, normal_positions);
|
||||
polygon
|
||||
.iter()
|
||||
.skip(1)
|
||||
.zip(polygon.iter().skip(2))
|
||||
.map(|(v1_index, v2_index)| {
|
||||
let (v1_vertex, v1_normal) =
|
||||
get_vertex_and_normal(v1_index, vertex_positions, normal_positions);
|
||||
let (v2_vertex, v2_normal) =
|
||||
get_vertex_and_normal(v2_index, vertex_positions, normal_positions);
|
||||
let vertices = [v0_vertex, v1_vertex, v2_vertex];
|
||||
let normals = [v0_normal, v1_normal, v2_normal];
|
||||
Triangle {
|
||||
vertices,
|
||||
normals,
|
||||
material: material.clone(),
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
} else {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_obj(
|
||||
filename: &Path,
|
||||
material: Arc<dyn Material>,
|
||||
) -> Result<Vec<Arc<dyn Primitive>>> {
|
||||
let obj = Obj::<SimplePolygon>::load(filename)?;
|
||||
|
||||
Ok(obj
|
||||
.objects
|
||||
.iter()
|
||||
.flat_map(|object| object.groups.iter())
|
||||
.flat_map(|group| group.polys.iter())
|
||||
.flat_map(|poly| get_triangles(poly, &obj.position, &obj.normal, material.clone()))
|
||||
.map(|triangle| Arc::new(triangle) as Arc<dyn Primitive>)
|
||||
.collect())
|
||||
}
|
||||
}
|
||||
|
||||
pub use wavefront_obj::load_obj;
|
||||
|
|
@ -1,60 +0,0 @@
|
|||
use std::f64::consts::PI;
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
use super::{RandomDistribution, UnitDisc};
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct CosineWeightedHemisphere {
|
||||
unit_disc: UnitDisc,
|
||||
}
|
||||
|
||||
impl CosineWeightedHemisphere {
|
||||
pub fn new() -> CosineWeightedHemisphere {
|
||||
let unit_disc = UnitDisc::new();
|
||||
CosineWeightedHemisphere { unit_disc }
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomDistribution<Vec3<f64>> for CosineWeightedHemisphere {
|
||||
fn value(&self) -> Vec3<f64> {
|
||||
let point_on_disc = self.unit_disc.value();
|
||||
let z = 0.0f64
|
||||
.max(
|
||||
1.0 - point_on_disc.x() * point_on_disc.x() - point_on_disc.y() * point_on_disc.y(),
|
||||
)
|
||||
.sqrt();
|
||||
Vec3::new(point_on_disc.x(), point_on_disc.y(), z)
|
||||
}
|
||||
|
||||
fn pdf(&self, v: Vec3<f64>) -> f64 {
|
||||
(v.x() * v.x() + v.y() * v.y()).sqrt() / PI
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_values() {
|
||||
let target = CosineWeightedHemisphere::new();
|
||||
for _ in 0..1000 {
|
||||
let value = target.value();
|
||||
println!("{}, {}, {}", value.x(), value.y(), value.z());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn integral_is_near_area() {
|
||||
let target = CosineWeightedHemisphere::new();
|
||||
let integral = (0..100000)
|
||||
.map(|_| target.value())
|
||||
.map(|value| 1.0 / target.pdf(value))
|
||||
.sum::<f64>()
|
||||
/ 100000.0;
|
||||
println!("Area: {}\nIntegral: {}", 2.0 * PI, integral);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
use rand::distributions::Open01;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use super::RandomDistribution;
|
||||
|
||||
pub struct LinearWeighted {
|
||||
max_value: f64,
|
||||
}
|
||||
|
||||
impl LinearWeighted {
|
||||
pub fn new(max_value: f64) -> LinearWeighted {
|
||||
LinearWeighted { max_value }
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomDistribution<f64> for LinearWeighted {
|
||||
fn value(&self) -> f64 {
|
||||
let mut rng = thread_rng();
|
||||
rng.sample::<f64, _>(Open01).sqrt() * self.max_value
|
||||
}
|
||||
|
||||
fn pdf(&self, value: f64) -> f64 {
|
||||
2.0 * value / (self.max_value * self.max_value)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_values() {
|
||||
let target = LinearWeighted::new(2.0);
|
||||
for _ in 0..1000 {
|
||||
let value = target.value();
|
||||
println!("{}", value);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_buckets() {
|
||||
let mut buckets = [0; 20];
|
||||
let target = LinearWeighted::new(20.0);
|
||||
for _ in 0..10000 {
|
||||
let value = target.value();
|
||||
let i = value as usize;
|
||||
buckets[i] += 1;
|
||||
}
|
||||
for count in buckets {
|
||||
println!("{}", count);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn integral_is_near_area() {
|
||||
let target = LinearWeighted::new(2.0);
|
||||
let integral = (0..100000)
|
||||
.map(|_| target.value())
|
||||
.map(|value| 1.0 / target.pdf(value))
|
||||
.sum::<f64>()
|
||||
/ 100000.0;
|
||||
println!("Area: {}\nIntegral: {}", 2.0, integral);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
mod uniform_square;
|
||||
pub use uniform_square::UniformSquare;
|
||||
|
||||
mod unit_disc;
|
||||
pub use unit_disc::UnitDisc;
|
||||
|
||||
mod uniform_hemisphere;
|
||||
pub use uniform_hemisphere::UniformHemisphere;
|
||||
|
||||
mod cosine_weighted_hemisphere;
|
||||
pub use cosine_weighted_hemisphere::CosineWeightedHemisphere;
|
||||
|
||||
mod linear_weighted;
|
||||
pub use linear_weighted::LinearWeighted;
|
||||
|
||||
mod sky_light_pdf;
|
||||
pub use sky_light_pdf::SkyLightPdf;
|
||||
|
||||
pub trait RandomDistribution<T> {
|
||||
fn value(&self) -> T;
|
||||
fn pdf(&self, value: T) -> f64;
|
||||
}
|
||||
|
|
@ -1,71 +0,0 @@
|
|||
use std::f64::consts::PI;
|
||||
|
||||
use rand::distributions::Open01;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
use super::{LinearWeighted, RandomDistribution};
|
||||
|
||||
pub struct SkyLightPdf {
|
||||
z_distribution: LinearWeighted,
|
||||
}
|
||||
|
||||
impl SkyLightPdf {
|
||||
pub fn new() -> SkyLightPdf {
|
||||
let z_distribution = LinearWeighted::new(1.0);
|
||||
SkyLightPdf { z_distribution }
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for SkyLightPdf {
|
||||
fn default() -> SkyLightPdf {
|
||||
SkyLightPdf::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomDistribution<Vec3<f64>> for SkyLightPdf {
|
||||
fn value(&self) -> Vec3<f64> {
|
||||
let mut rng = thread_rng();
|
||||
let phi = rng.sample::<f64, _>(Open01) * 2.0 * PI;
|
||||
let z = self.z_distribution.value();
|
||||
let r = (1.0 - z * z).sqrt();
|
||||
Vec3::new(r * phi.cos(), r * phi.sin(), z)
|
||||
}
|
||||
|
||||
fn pdf(&self, value: Vec3<f64>) -> f64 {
|
||||
let z = value.z();
|
||||
if z < 0.0 {
|
||||
0.0
|
||||
} else {
|
||||
z / PI
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_values() {
|
||||
let target = SkyLightPdf::new();
|
||||
for _ in 0..1000 {
|
||||
let value = target.value();
|
||||
println!("{}, {}, {}", value.x(), value.y(), value.z());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn integral_is_near_area() {
|
||||
let target = SkyLightPdf::new();
|
||||
let integral = (0..100000)
|
||||
.map(|_| target.value())
|
||||
.map(|value| 1.0 / target.pdf(value))
|
||||
.sum::<f64>()
|
||||
/ 100000.0;
|
||||
println!("Area: {}\nIntegral: {}", 2.0 * PI, integral);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,67 +0,0 @@
|
|||
use std::f64::consts::PI;
|
||||
|
||||
use rand::distributions::{Open01, OpenClosed01};
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
use super::RandomDistribution;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UniformHemisphere {}
|
||||
|
||||
impl UniformHemisphere {
|
||||
pub fn new() -> UniformHemisphere {
|
||||
UniformHemisphere {}
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomDistribution<Vec3<f64>> for UniformHemisphere {
|
||||
fn value(&self) -> Vec3<f64> {
|
||||
let mut rng = thread_rng();
|
||||
let mut result = Vec3::new(
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
rng.sample::<f64, _>(OpenClosed01),
|
||||
);
|
||||
while result.norm_squared() > 1.0 {
|
||||
result = Vec3::new(
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
2.0 * rng.sample::<f64, _>(Open01) - 1.0,
|
||||
rng.sample::<f64, _>(OpenClosed01),
|
||||
);
|
||||
}
|
||||
result.normalize()
|
||||
}
|
||||
|
||||
fn pdf(&self, _: Vec3<f64>) -> f64 {
|
||||
1.0 / (2.0 * PI)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_values() {
|
||||
let target = UniformHemisphere::new();
|
||||
for _ in 0..1000 {
|
||||
let value = target.value();
|
||||
println!("{}, {}, {}", value.x(), value.y(), value.z());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn integral_is_near_area() {
|
||||
let target = UniformHemisphere::new();
|
||||
let integral = (0..1000)
|
||||
.map(|_| target.value())
|
||||
.map(|value| 1.0 / target.pdf(value))
|
||||
.sum::<f64>()
|
||||
/ 1000.0;
|
||||
println!("Area: {}\nIntegral: {}", 2.0 * PI, integral);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,63 +0,0 @@
|
|||
use rand::distributions::Open01;
|
||||
use rand::{thread_rng, Rng};
|
||||
|
||||
use crate::math::Vec2;
|
||||
|
||||
use super::RandomDistribution;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UniformSquare {
|
||||
corner: Vec2<f64>,
|
||||
size: f64,
|
||||
}
|
||||
|
||||
impl UniformSquare {
|
||||
pub fn new(corner: Vec2<f64>, size: f64) -> UniformSquare {
|
||||
UniformSquare { corner, size }
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomDistribution<Vec2<f64>> for UniformSquare {
|
||||
fn value(&self) -> Vec2<f64> {
|
||||
let mut rng = thread_rng();
|
||||
self.corner
|
||||
+ Vec2::new(rng.sample::<f64, _>(Open01), rng.sample::<f64, _>(Open01)) * self.size
|
||||
}
|
||||
|
||||
fn pdf(&self, _value: Vec2<f64>) -> f64 {
|
||||
1.0 / (self.size * self.size)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_values() {
|
||||
let target = UniformSquare {
|
||||
corner: Vec2::new(1.5, -2.5),
|
||||
size: 3.0,
|
||||
};
|
||||
for _ in 0..1000 {
|
||||
let value = target.value();
|
||||
println!("{}, {}", value.x(), value.y());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn integral_is_near_area() {
|
||||
let target = UniformSquare {
|
||||
corner: Vec2::new(1.5, -2.5),
|
||||
size: 3.0,
|
||||
};
|
||||
let integral = (0..1000)
|
||||
.map(|_| target.value())
|
||||
.map(|value| 1.0 / target.pdf(value))
|
||||
.sum::<f64>()
|
||||
/ 1000.0;
|
||||
println!("Area: {}\nIntegral: {}", 3.0 * 3.0, integral);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,72 +0,0 @@
|
|||
use std::f64::consts::PI;
|
||||
|
||||
use crate::math::Vec2;
|
||||
|
||||
use super::{RandomDistribution, UniformSquare};
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct UnitDisc {
|
||||
square_distribution: UniformSquare,
|
||||
}
|
||||
|
||||
impl Default for UnitDisc {
|
||||
fn default() -> UnitDisc {
|
||||
UnitDisc::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl UnitDisc {
|
||||
pub fn new() -> UnitDisc {
|
||||
let square_distribution = UniformSquare::new(Vec2::new(-1.0, -1.0), 2.0);
|
||||
UnitDisc {
|
||||
square_distribution,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl RandomDistribution<Vec2<f64>> for UnitDisc {
|
||||
fn value(&self) -> Vec2<f64> {
|
||||
let offset = self.square_distribution.value();
|
||||
if offset.x() == 0.0 && offset.y() == 0.0 {
|
||||
offset
|
||||
} else {
|
||||
let (radius, angle) = if offset.x().abs() > offset.y().abs() {
|
||||
(offset.x(), (PI / 4.0) * offset.y() / offset.x())
|
||||
} else {
|
||||
(offset.y(), PI / 2.0 - (PI / 4.0) * offset.x() / offset.y())
|
||||
};
|
||||
Vec2::new(angle.cos(), angle.sin()) * radius
|
||||
}
|
||||
}
|
||||
|
||||
fn pdf(&self, _: Vec2<f64>) -> f64 {
|
||||
1.0 / PI
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn print_values() {
|
||||
let target = UnitDisc::new();
|
||||
for _ in 0..1000 {
|
||||
let value = target.value();
|
||||
println!("{}, {}", value.x(), value.y());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[ignore]
|
||||
fn integral_is_near_area() {
|
||||
let target = UnitDisc::new();
|
||||
let integral = (0..1000)
|
||||
.map(|_| target.value())
|
||||
.map(|value| 1.0 / target.pdf(value))
|
||||
.sum::<f64>()
|
||||
/ 1000.0;
|
||||
println!("Area: {}\nIntegral: {}", PI, integral);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,375 @@
|
|||
use nalgebra::{convert, RealField, Vector3};
|
||||
|
||||
use super::materials::Material;
|
||||
|
||||
use std::rc::Rc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Ray<T: RealField> {
|
||||
origin: Vector3<T>,
|
||||
direction: Vector3<T>,
|
||||
}
|
||||
|
||||
impl<T: RealField> Ray<T> {
|
||||
pub fn new(origin: Vector3<T>, direction: Vector3<T>) -> Ray<T> {
|
||||
Ray {
|
||||
origin,
|
||||
direction: direction.normalize(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn point_at(&self, t: T) -> Vector3<T> {
|
||||
return self.origin + self.direction * t;
|
||||
}
|
||||
|
||||
pub fn bias(&self, amount: T) -> Ray<T> {
|
||||
Ray::new(self.origin + self.direction * amount, self.direction)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct IntersectionInfo<T: RealField> {
|
||||
pub distance: T,
|
||||
pub location: Vector3<T>,
|
||||
pub normal: Vector3<T>,
|
||||
pub tangent: Vector3<T>,
|
||||
pub cotangent: Vector3<T>,
|
||||
pub retro: Vector3<T>,
|
||||
pub material: Rc<dyn Material<T>>,
|
||||
}
|
||||
|
||||
pub trait Intersect<T: RealField> {
|
||||
fn intersect<'a>(&'a self, ray: &Ray<T>) -> Option<IntersectionInfo<T>>;
|
||||
}
|
||||
|
||||
pub struct Sphere<T: RealField> {
|
||||
centre: Vector3<T>,
|
||||
radius: T,
|
||||
material: Rc<dyn Material<T>>,
|
||||
}
|
||||
|
||||
impl<T: RealField> Sphere<T> {
|
||||
pub fn new(centre: Vector3<T>, radius: T, material: Rc<dyn Material<T>>) -> Sphere<T> {
|
||||
Sphere {
|
||||
centre,
|
||||
radius,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RealField> Intersect<T> for Sphere<T> {
|
||||
fn intersect<'a>(&'a self, ray: &Ray<T>) -> Option<IntersectionInfo<T>> {
|
||||
/*let ray_origin_to_sphere_centre = self.centre - ray.origin;
|
||||
let radius_squared = self.radius * self.radius;
|
||||
let is_inside_sphere = ray_origin_to_sphere_centre.norm_squared() <= radius_squared;
|
||||
// t0/p0 is the point on the ray that's closest to the centre of the sphere
|
||||
// ray.direction is normalized, so it's not necessary to divide by its length.
|
||||
let t0 = ray_origin_to_sphere_centre.dot(&ray.direction);
|
||||
if !is_inside_sphere && t0 < T::zero() {
|
||||
// Sphere is behind ray origin
|
||||
return None;
|
||||
}
|
||||
// Squared distance between ray origin and sphere centre
|
||||
let d0_squared = (ray_origin_to_sphere_centre).norm_squared();
|
||||
// p0, ray.origin and sphere.centre form a right triangle, with p0 at the right corner,
|
||||
// Squared distance petween p0 and sphere centre, using Pythagoras
|
||||
let p0_dist_from_centre_squared = d0_squared - t0 * t0;
|
||||
if p0_dist_from_centre_squared > radius_squared {
|
||||
// Sphere is in front of ray but ray misses
|
||||
return None;
|
||||
}
|
||||
let p0_dist_from_centre =p0_dist_from_centre_squared.sqrt();
|
||||
// Two more right triangles are formed by p0, the sphere centre, and the two places
|
||||
// where the ray intersects the sphere. (Or the ray may be a tangent to the sphere
|
||||
// in which case these triangles are degenerate. Here we use Pythagoras again to find
|
||||
.// find the distance between p0 and the two intersection points.
|
||||
let delta = (radius_squared - p0_dist_from_centre_squared).sqrt();
|
||||
let distance = if is_inside_sphere {
|
||||
// radius origin is inside sphere
|
||||
t0 + delta
|
||||
} else {
|
||||
t0 - delta
|
||||
};
|
||||
let location = ray.point_at(distance);
|
||||
let normal = (location - self.centre).normalize();
|
||||
let tangent = normal.cross(&Vector3::z_axis());
|
||||
let cotangent = normal.cross(&tangent);
|
||||
let retro = -ray.direction;*/
|
||||
let two: T = convert(2.0);
|
||||
let four: T = convert(4.0);
|
||||
let a = ray
|
||||
.direction
|
||||
.component_mul(&ray.direction)
|
||||
.iter()
|
||||
.fold(T::zero(), |a, b| a + *b);
|
||||
let b = ((ray.origin.component_mul(&ray.direction)
|
||||
- self.centre.component_mul(&ray.direction))
|
||||
* two)
|
||||
.iter()
|
||||
.fold(T::zero(), |a, b| a + *b);
|
||||
let c = (ray.origin.component_mul(&ray.origin) + self.centre.component_mul(&self.centre)
|
||||
- self.centre.component_mul(&ray.origin) * two)
|
||||
.iter()
|
||||
.fold(T::zero(), |a, b| a + *b)
|
||||
- self.radius * self.radius;
|
||||
let delta_squared: T = b * b - four * a * c;
|
||||
if delta_squared < T::zero() {
|
||||
None
|
||||
} else {
|
||||
let delta = delta_squared.sqrt();
|
||||
let one_over_2_a = T::one() / (two * a);
|
||||
let t1 = (-b - delta) * one_over_2_a;
|
||||
let t2 = (-b + delta) * one_over_2_a;
|
||||
let distance = if t1 < T::zero() {
|
||||
t2
|
||||
} else if t2 < T::zero() {
|
||||
t1
|
||||
} else if t1 < t2 {
|
||||
t1
|
||||
} else {
|
||||
t2
|
||||
};
|
||||
if distance <= T::zero() {
|
||||
None
|
||||
} else {
|
||||
let location = ray.point_at(distance);
|
||||
let normal = (location - self.centre).normalize();
|
||||
let tangent = normal.cross(&Vector3::z_axis());
|
||||
let cotangent = normal.cross(&tangent);
|
||||
let retro = -ray.direction;
|
||||
Some(IntersectionInfo {
|
||||
distance,
|
||||
location,
|
||||
normal,
|
||||
tangent,
|
||||
cotangent,
|
||||
retro,
|
||||
material: Rc::clone(&self.material),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Plane<T: RealField> {
|
||||
normal: Vector3<T>,
|
||||
tangent: Vector3<T>,
|
||||
cotangent: Vector3<T>,
|
||||
distance_from_origin: T,
|
||||
material: Rc<dyn Material<T>>,
|
||||
}
|
||||
|
||||
impl<T: RealField> Plane<T> {
|
||||
pub fn new(
|
||||
normal: Vector3<T>,
|
||||
distance_from_origin: T,
|
||||
material: Rc<dyn Material<T>>,
|
||||
) -> Plane<T> {
|
||||
normal.normalize();
|
||||
let mut axis_closest_to_tangent = Vector3::zeros();
|
||||
axis_closest_to_tangent[normal.iamin()] = T::one();
|
||||
let cotangent = normal.cross(&axis_closest_to_tangent);
|
||||
let tangent = normal.cross(&cotangent);
|
||||
Plane {
|
||||
normal,
|
||||
tangent,
|
||||
cotangent,
|
||||
distance_from_origin,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: RealField> Intersect<T> for Plane<T> {
|
||||
fn intersect<'a>(&'a self, ray: &Ray<T>) -> Option<IntersectionInfo<T>> {
|
||||
let ray_direction_dot_plane_normal = ray.direction.dot(&self.normal);
|
||||
let point_on_plane = self.normal * self.distance_from_origin;
|
||||
let point_on_plane_minus_ray_origin_dot_normal =
|
||||
(point_on_plane - ray.origin).dot(&self.normal);
|
||||
if ray_direction_dot_plane_normal == convert(0.0) {
|
||||
//Ray is parallel to plane
|
||||
if point_on_plane_minus_ray_origin_dot_normal != convert(0.0) {
|
||||
//Ray is not in plane
|
||||
return None;
|
||||
}
|
||||
}
|
||||
let t = point_on_plane_minus_ray_origin_dot_normal / ray_direction_dot_plane_normal;
|
||||
if t < convert(0.0) {
|
||||
return None;
|
||||
}
|
||||
Some(IntersectionInfo {
|
||||
distance: t,
|
||||
location: ray.point_at(t),
|
||||
normal: self.normal,
|
||||
tangent: self.tangent,
|
||||
cotangent: self.cotangent,
|
||||
retro: -ray.direction,
|
||||
material: Rc::clone(&self.material),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
macro_rules! assert_matches {
|
||||
($expression:expr, $($pattern:tt)+) => {
|
||||
match $expression {
|
||||
$($pattern)+ => (),
|
||||
ref e => panic!("assertion failed: `{:?}` does not match `{}`", e,
|
||||
stringify!($($pattern)+)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
use super::*;
|
||||
use crate::materials::LambertianMaterial;
|
||||
use quickcheck::{Arbitrary, Gen, TestResult};
|
||||
impl<T: Arbitrary + RealField> Arbitrary for Ray<T> {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Ray<T> {
|
||||
let origin = <Vector3<T> as Arbitrary>::arbitrary(g);
|
||||
let direction = <Vector3<T> as Arbitrary>::arbitrary(g);
|
||||
return Ray::new(origin, direction);
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn t0_is_origin(ray: Ray<f64>) -> bool {
|
||||
ray.point_at(0.0) == ray.origin
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn t1_is_origin_plus_direction(ray: Ray<f64>) -> bool {
|
||||
ray.point_at(1.0) == ray.origin + ray.direction
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn points_are_colinear(ray: Ray<f64>, t1: f64, t2: f64, t3: f64) -> bool {
|
||||
let p1 = ray.point_at(t1);
|
||||
let p2 = ray.point_at(t2);
|
||||
let p3 = ray.point_at(t3);
|
||||
let epsilon = [t1, t2, t3, ray.origin[0], ray.origin[1], ray.origin[2]]
|
||||
.iter()
|
||||
.fold(0.0, |a, &b| a.max(b.abs()))
|
||||
* std::f64::EPSILON
|
||||
* 256.0;
|
||||
(p2 - p1).cross(&(p3 - p2)).norm() < epsilon
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn t_is_distance(ray: Ray<f64>, t: f64) -> bool {
|
||||
(ray.point_at(t) - ray.origin).norm() - t.abs() < 0.0000000001
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_sphere() {
|
||||
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vector3::new(1.5, 1.5, 15.0),
|
||||
5.0,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
assert_matches!(s.intersect(&r), Some(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_does_not_intersect_sphere_when_sphere_is_in_front() {
|
||||
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vector3::new(-5.0, 1.5, 15.0),
|
||||
5.0,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
assert_matches!(s.intersect(&r), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_does_not_intersect_sphere_when_sphere_is_behind() {
|
||||
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vector3::new(1.5, 1.5, -15.0),
|
||||
5.0,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
assert_matches!(s.intersect(&r), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_sphere_when_origin_is_inside() {
|
||||
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vector3::new(1.5, 1.5, 2.0),
|
||||
5.0,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
assert_matches!(s.intersect(&r), Some(_));
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn ray_intersects_sphere_centre_at_correct_distance(
|
||||
ray_origin: Vector3<f64>,
|
||||
sphere_centre: Vector3<f64>,
|
||||
radius: f64,
|
||||
) -> TestResult {
|
||||
if radius <= 0.0 || radius + 0.000001 >= (ray_origin - sphere_centre).norm() {
|
||||
return TestResult::discard();
|
||||
};
|
||||
let sphere = Sphere::new(
|
||||
sphere_centre,
|
||||
radius,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let ray = Ray::new(ray_origin, sphere_centre - ray_origin);
|
||||
let info = sphere.intersect(&ray).unwrap();
|
||||
let distance_to_centre = (sphere_centre - ray.origin).norm();
|
||||
TestResult::from_bool(
|
||||
(distance_to_centre - (info.distance + sphere.radius)).abs() < 0.00001,
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_plane() {
|
||||
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(-1.0, 0.0, 1.0));
|
||||
let p = Plane::new(
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
-5.0,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
assert_matches!(p.intersect(&r), Some(_));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_does_not_intersect_plane() {
|
||||
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(1.0, 0.0, 1.0));
|
||||
let p = Plane::new(
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
-5.0,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
assert_matches!(p.intersect(&r), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_point_is_on_plane() {
|
||||
let r = Ray::new(Vector3::new(1.0, 2.0, 3.0), Vector3::new(-1.0, 0.0, 1.0));
|
||||
let p = Plane::new(
|
||||
Vector3::new(1.0, 0.0, 0.0),
|
||||
-5.0,
|
||||
Rc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
match p.intersect(&r) {
|
||||
Some(IntersectionInfo {
|
||||
distance: _,
|
||||
location,
|
||||
normal: _,
|
||||
tangent: _,
|
||||
cotangent: _,
|
||||
retro: _,
|
||||
material: _,
|
||||
}) => assert!((location.x - (-5.0f64)).abs() < 0.0000000001),
|
||||
None => panic!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,124 +0,0 @@
|
|||
use crate::util::Interval;
|
||||
|
||||
use super::{IntersectP, Ray};
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
pub use crate::util::axis_aligned_bounding_box::BoundingBox;
|
||||
|
||||
impl IntersectP for BoundingBox {
|
||||
fn intersect(&self, ray: &Ray) -> bool {
|
||||
let mut t_interval_in_bounds = Interval::infinite();
|
||||
for (&ray_origin, &ray_direction, bounds) in izip!(
|
||||
ray.origin.coords.iter(),
|
||||
ray.direction.coords.iter(),
|
||||
self.bounds.iter()
|
||||
) {
|
||||
t_interval_in_bounds = t_interval_in_bounds.intersection(Interval::new(
|
||||
(bounds.get_min() - ray_origin) / ray_direction,
|
||||
(bounds.get_max() - ray_origin) / ray_direction,
|
||||
));
|
||||
if t_interval_in_bounds.is_empty() {
|
||||
return false;
|
||||
};
|
||||
}
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
use quickcheck::TestResult;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
fn wrap_value_in_interval(value: f64, interval: Interval) -> f64 {
|
||||
let distance_from_start = (value - interval.get_min()).abs();
|
||||
let range = interval.get_max() - interval.get_min();
|
||||
let multiple_of_range = distance_from_start / range;
|
||||
return interval.get_min() + multiple_of_range.fract() * range;
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn wrap_value_in_interval_produces_values_in_interval(v: f64, a: f64, b: f64) -> bool {
|
||||
let interval = Interval::new(a, b);
|
||||
interval.contains_value(wrap_value_in_interval(v, interval))
|
||||
}
|
||||
|
||||
fn wrap_point_into_bounding_box(point: Vec3<f64>, bounds: &BoundingBox) -> Vec3<f64> {
|
||||
let mut coords = [0.0; 3];
|
||||
for i in 0..3 {
|
||||
coords[i] = wrap_value_in_interval(point[i], bounds.bounds[i]);
|
||||
}
|
||||
Vec3 { coords }
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn correctly_detects_intersections(
|
||||
ray_origin: Vec3<f64>,
|
||||
corner1: Vec3<f64>,
|
||||
corner2: Vec3<f64>,
|
||||
random_point: Vec3<f64>,
|
||||
) -> bool {
|
||||
let bounds = BoundingBox::from_corners(corner1, corner2);
|
||||
let point_in_bounds = wrap_point_into_bounding_box(random_point, &bounds);
|
||||
let ray = Ray::new(ray_origin, point_in_bounds - ray_origin);
|
||||
bounds.intersect(&ray)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersect_always_true_when_ray_origin_is_inside_bounds(
|
||||
ray_origin: Vec3<f64>,
|
||||
corner1: Vec3<f64>,
|
||||
corner2: Vec3<f64>,
|
||||
random_point: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
let bounds = BoundingBox::from_corners(corner1, corner2);
|
||||
let ray_origin = wrap_point_into_bounding_box(ray_origin, &bounds);
|
||||
let ray = Ray::new(ray_origin, ray_origin - random_point);
|
||||
TestResult::from_bool(bounds.intersect(&ray))
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn no_intersection_when_behind_ray(
|
||||
ray_origin: Vec3<f64>,
|
||||
corner1: Vec3<f64>,
|
||||
corner2: Vec3<f64>,
|
||||
random_point: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
let bounds = BoundingBox::from_corners(corner1, corner2);
|
||||
if bounds.contains_point(ray_origin) {
|
||||
return TestResult::discard();
|
||||
}
|
||||
let point_in_bounds = wrap_point_into_bounding_box(random_point, &bounds);
|
||||
let ray = Ray::new(ray_origin, ray_origin - point_in_bounds);
|
||||
TestResult::from_bool(bounds.intersect(&ray))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_detected_when_ray_parallel_to_axis() {
|
||||
let target =
|
||||
BoundingBox::from_corners(Vec3::new(1.0f64, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0));
|
||||
let x_ray = Ray::new(Vec3::new(0.0, 3.0, 4.0), Vec3::new(1.0, 0.0, 0.0));
|
||||
assert!(target.intersect(&x_ray));
|
||||
let y_ray = Ray::new(Vec3::new(2.0, 0.0, 4.0), Vec3::new(0.0, 1.0, 0.0));
|
||||
assert!(target.intersect(&y_ray));
|
||||
let z_ray = Ray::new(Vec3::new(2.0, 3.0, 0.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
assert!(target.intersect(&z_ray));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_missed_when_ray_parallel_to_axis() {
|
||||
let target =
|
||||
BoundingBox::from_corners(Vec3::new(1.0f64, 2.0, 3.0), Vec3::new(4.0, 5.0, 6.0));
|
||||
let x_ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(1.0, 0.0, 0.0));
|
||||
assert!(!target.intersect(&x_ray));
|
||||
let y_ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 1.0, 0.0));
|
||||
assert!(!target.intersect(&y_ray));
|
||||
let z_ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
assert!(!target.intersect(&z_ray));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,133 +0,0 @@
|
|||
use crate::math::{Affine3, Vec3};
|
||||
|
||||
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectP, IntersectionInfo, Primitive, Ray};
|
||||
|
||||
use std::cmp::Ordering;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// Stores a set of [Primitives](Primitive) and accelerates raycasting
|
||||
///
|
||||
/// Organizes the primitives into a binary tree based on their bounds, allowing the
|
||||
/// closest intersection with a ray to be found efficiently.
|
||||
///
|
||||
/// Each node knows the overall bounds of all it's children, which means that a ray that
|
||||
/// doesn't intersect the [BoundingBox](BoundingBox) of the node doesn't intersect any of
|
||||
/// the primitives stored in it's children.
|
||||
pub enum BoundingVolumeHierarchy {
|
||||
Node {
|
||||
bounds: BoundingBox,
|
||||
left: Box<BoundingVolumeHierarchy>,
|
||||
right: Box<BoundingVolumeHierarchy>,
|
||||
},
|
||||
Leaf {
|
||||
bounds: BoundingBox,
|
||||
primitives: Vec<Arc<dyn Primitive>>,
|
||||
},
|
||||
}
|
||||
|
||||
fn centre(bounds: &BoundingBox) -> Vec3<f64> {
|
||||
Vec3::new(
|
||||
(bounds.bounds[0].get_min() + bounds.bounds[0].get_max()) / 2.00,
|
||||
(bounds.bounds[1].get_min() + bounds.bounds[1].get_max()) / 2.0,
|
||||
(bounds.bounds[2].get_min() + bounds.bounds[2].get_max()) / 2.0,
|
||||
)
|
||||
}
|
||||
|
||||
fn heuristic_split(primitives: &mut [Arc<dyn Primitive>], bounds: &BoundingBox) -> usize {
|
||||
let largest_dimension = bounds.largest_dimension();
|
||||
primitives.sort_unstable_by(|a, b| {
|
||||
centre(&a.bounding_box())[largest_dimension]
|
||||
.partial_cmp(¢re(&b.bounding_box())[largest_dimension])
|
||||
.unwrap_or(Ordering::Equal)
|
||||
});
|
||||
primitives.len() / 2
|
||||
}
|
||||
|
||||
impl BoundingVolumeHierarchy {
|
||||
pub fn build(primitives: &mut [Arc<dyn Primitive>]) -> Self {
|
||||
BoundingVolumeHierarchy::build_from_slice(primitives)
|
||||
}
|
||||
|
||||
pub fn build_from_slice(primitives: &mut [Arc<dyn Primitive>]) -> Self {
|
||||
let bounds = primitives
|
||||
.iter()
|
||||
.fold(BoundingBox::empty(), |acc, p| acc.union(&p.bounding_box()));
|
||||
if primitives.len() <= 1 {
|
||||
let primitives = primitives.to_vec();
|
||||
BoundingVolumeHierarchy::Leaf { bounds, primitives }
|
||||
} else {
|
||||
let pivot = heuristic_split(primitives, &bounds);
|
||||
let left = Box::new(BoundingVolumeHierarchy::build_from_slice(
|
||||
&mut primitives[0..pivot],
|
||||
));
|
||||
let right = Box::new(BoundingVolumeHierarchy::build_from_slice(
|
||||
&mut primitives[pivot..],
|
||||
));
|
||||
BoundingVolumeHierarchy::Node {
|
||||
bounds,
|
||||
left,
|
||||
right,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn closest_intersection(
|
||||
a: Option<IntersectionInfo>,
|
||||
b: Option<IntersectionInfo>,
|
||||
) -> Option<IntersectionInfo> {
|
||||
match a {
|
||||
None => b,
|
||||
Some(a_info) => match b {
|
||||
None => Some(a_info),
|
||||
Some(b_info) => Some(if a_info.distance < b_info.distance {
|
||||
a_info
|
||||
} else {
|
||||
b_info
|
||||
}),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl Intersect for BoundingVolumeHierarchy {
|
||||
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo> {
|
||||
match self {
|
||||
BoundingVolumeHierarchy::Node {
|
||||
bounds,
|
||||
left,
|
||||
right,
|
||||
} => {
|
||||
if bounds.intersect(ray) {
|
||||
closest_intersection(left.intersect(ray), right.intersect(ray))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
BoundingVolumeHierarchy::Leaf { bounds, primitives } => {
|
||||
if bounds.intersect(ray) {
|
||||
primitives
|
||||
.iter()
|
||||
.map(|elem| elem.intersect(ray))
|
||||
.fold(None, closest_intersection)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasBoundingBox for BoundingVolumeHierarchy {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
BoundingBox::empty()
|
||||
}
|
||||
}
|
||||
|
||||
impl Primitive for BoundingVolumeHierarchy {
|
||||
fn transform(&self, transformation: &Affine3<f64>) -> Box<dyn Primitive> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {}
|
||||
|
|
@ -1,171 +0,0 @@
|
|||
use crate::math::{Affine3,Vec3};
|
||||
|
||||
use super::materials::Material;
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub mod sphere;
|
||||
pub use sphere::Sphere;
|
||||
|
||||
pub mod plane;
|
||||
pub use plane::Plane;
|
||||
|
||||
pub mod triangle;
|
||||
pub use triangle::Triangle;
|
||||
|
||||
pub mod axis_aligned_bounding_box;
|
||||
pub use axis_aligned_bounding_box::BoundingBox;
|
||||
|
||||
pub mod bounding_volume_hierarchy;
|
||||
pub use bounding_volume_hierarchy::BoundingVolumeHierarchy;
|
||||
|
||||
pub mod vec_aggregate;
|
||||
|
||||
/// A ray, consisting or a start point and direction
|
||||
///
|
||||
/// This is the basic ray struct used to define things like a line-of-sight
|
||||
/// going out from the camera of a reflection from a surface.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Ray {
|
||||
/// The start point of the ray
|
||||
pub origin: Vec3<f64>,
|
||||
|
||||
/// The direction the ray goes in.
|
||||
///
|
||||
/// This vector should always be kept normalized
|
||||
pub direction: Vec3<f64>,
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
/// Create a new ray
|
||||
pub fn new(origin: Vec3<f64>, direction: Vec3<f64>) -> Ray {
|
||||
Ray {
|
||||
origin,
|
||||
direction: direction.normalize(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the point on the ray that is `t` units from the start
|
||||
pub fn point_at(&self, t: f64) -> Vec3<f64> {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
|
||||
/// Create a new ray by moving the original ray along it's direction by `amount`
|
||||
///
|
||||
/// `amount` is normally a very small number. This function is useful for ensuring
|
||||
/// that rounding-errors don;t cause a reflection ray doesn't intersect with the point
|
||||
/// it's reflected from.
|
||||
pub fn bias(&self, amount: f64) -> Ray {
|
||||
Ray::new(self.origin + self.direction * amount, self.direction)
|
||||
}
|
||||
}
|
||||
|
||||
/// Information about a ray-primitive intersection.
|
||||
///
|
||||
/// This struct is returned by [intersect()](Intersect::intersect) and contatins all the
|
||||
/// information needed to evaluate the rendering function for that intersection.
|
||||
#[derive(Debug)]
|
||||
pub struct IntersectionInfo {
|
||||
/// The distance between the ray origin and the intersection point
|
||||
pub distance: f64,
|
||||
|
||||
/// The intersection point
|
||||
pub location: Vec3<f64>,
|
||||
|
||||
/// The surface normal at the intersection point
|
||||
pub normal: Vec3<f64>,
|
||||
|
||||
/// The surface tangent at the intersection point
|
||||
///
|
||||
/// Which surface tangent direction returned is dependent on the [Primitive](Primitive)
|
||||
/// but should generally be smooth over any given surface
|
||||
pub tangent: Vec3<f64>,
|
||||
|
||||
/// Another surface tangent, perpendicular to `tangent`
|
||||
///
|
||||
/// The cross product or `normal` and `tangent`
|
||||
pub cotangent: Vec3<f64>,
|
||||
|
||||
/// The direction from the intersection point back towards the ray
|
||||
///
|
||||
/// Equal to `-ray.direction`
|
||||
pub retro: Vec3<f64>,
|
||||
|
||||
/// The [Material](crate::materials::Material) which describes the optical
|
||||
/// properties of the intersected surface
|
||||
pub material: Arc<dyn Material>,
|
||||
}
|
||||
|
||||
/// A geometric object that has a [Material](crate::materials::Material) and can be
|
||||
/// intersected with a [Ray](Ray)
|
||||
pub trait Intersect: Send + Sync {
|
||||
/// Test if the ray intersects the object, and return information about the object and intersection.
|
||||
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo>;
|
||||
}
|
||||
|
||||
/// A geometric object that can be intersected with a ray
|
||||
///
|
||||
/// This is useful for objects that don't have materials (such as [BoundingBox](BoundingBox))
|
||||
/// and as a (possibly) faster alternative to [Intersect](Intersect) when only a simple
|
||||
/// intersection test is needed.
|
||||
pub trait IntersectP: Send + Sync {
|
||||
/// Test if the ray intersects the object, without calculating any extra information.
|
||||
fn intersect(&self, ray: &Ray) -> bool;
|
||||
}
|
||||
|
||||
/// Any geometric object for which a [BoundingBox](BoundingBox) can be calculated
|
||||
pub trait HasBoundingBox: Send + Sync {
|
||||
/// The axis-aligned bounding box of the object
|
||||
///
|
||||
/// The object must fit entirely inside this box.
|
||||
fn bounding_box(&self) -> BoundingBox;
|
||||
}
|
||||
|
||||
/// A basic geometric primitive such as a sphere or a triangle
|
||||
pub trait Primitive: Intersect + HasBoundingBox {
|
||||
// / Create a new object by applying the transformation to this object.
|
||||
fn transform(&self, transformation: &Affine3<f64>) -> Box<dyn Primitive>;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
use super::*;
|
||||
use quickcheck::{Arbitrary, Gen};
|
||||
impl Arbitrary for Ray {
|
||||
fn arbitrary<G: Gen>(g: &mut G) -> Ray {
|
||||
let origin = <Vec3<f64> as Arbitrary>::arbitrary(g);
|
||||
let direction = <Vec3<f64> as Arbitrary>::arbitrary(g);
|
||||
return Ray::new(origin, direction);
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn t0_is_origin(ray: Ray) -> bool {
|
||||
ray.point_at(0.0) == ray.origin
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn t1_is_origin_plus_direction(ray: Ray) -> bool {
|
||||
ray.point_at(1.0) == ray.origin + ray.direction
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn points_are_colinear(ray: Ray, t1: f64, t2: f64, t3: f64) -> bool {
|
||||
let p1 = ray.point_at(t1);
|
||||
let p2 = ray.point_at(t2);
|
||||
let p3 = ray.point_at(t3);
|
||||
let epsilon = [t1, t2, t3, ray.origin[0], ray.origin[1], ray.origin[2]]
|
||||
.iter()
|
||||
.fold(0.0f64, |a, &b| a.max(b.abs()))
|
||||
* std::f64::EPSILON
|
||||
* 256.0f64;
|
||||
(p2 - p1).cross(&(p3 - p2)).norm() < epsilon
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn t_is_distance(ray: Ray, t: f64) -> bool {
|
||||
(ray.point_at(t) - ray.origin).norm() - t.abs() < 0.0000000001
|
||||
}
|
||||
}
|
||||
|
|
@ -1,290 +0,0 @@
|
|||
use crate::materials::Material;
|
||||
use crate::math::Vec3;
|
||||
|
||||
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectionInfo, Primitive, Ray};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Plane {
|
||||
normal: Vec3<f64>,
|
||||
tangent: Vec3<f64>,
|
||||
cotangent: Vec3<f64>,
|
||||
distance_from_origin: f64,
|
||||
material: Arc<dyn Material>,
|
||||
}
|
||||
|
||||
impl Plane {
|
||||
pub fn new(normal: Vec3<f64>, distance_from_origin: f64, material: Arc<dyn Material>) -> Plane {
|
||||
let normal = normal.normalize();
|
||||
let mut axis_closest_to_tangent = Vec3::zeros();
|
||||
axis_closest_to_tangent[normal.smallest_coord()] = 1.0;
|
||||
let cotangent = normal.cross(&axis_closest_to_tangent).normalize();
|
||||
let tangent = normal.cross(&cotangent);
|
||||
Plane {
|
||||
normal,
|
||||
tangent,
|
||||
cotangent,
|
||||
distance_from_origin,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*impl Transform for Plane {
|
||||
fn transform(&self, transformation: &Affine3<f64>) -> Self {
|
||||
Plane {
|
||||
normal: transformation.transform_vector(&self.normal).normalize(),
|
||||
cotangent: transformation.transform_vector(&self.cotangent).normalize(),
|
||||
tangent: self.normal.cross(&self.cotangent),
|
||||
distance_from_origin: transformation
|
||||
.transform_vector(&(self.normal * self.distance_from_origin))
|
||||
.norm(),
|
||||
material: Arc::clone(&self.material),
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl Intersect for Plane {
|
||||
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo> {
|
||||
let ray_direction_dot_plane_normal = ray.direction.dot(&self.normal);
|
||||
let point_on_plane = self.normal * self.distance_from_origin;
|
||||
let point_on_plane_minus_ray_origin_dot_normal =
|
||||
(point_on_plane - ray.origin).dot(&self.normal);
|
||||
if ray_direction_dot_plane_normal == 0.0 {
|
||||
//Ray is parallel to plane
|
||||
if point_on_plane_minus_ray_origin_dot_normal != 0.0 {
|
||||
//Ray is not in plane
|
||||
return None;
|
||||
}
|
||||
}
|
||||
let t = point_on_plane_minus_ray_origin_dot_normal / ray_direction_dot_plane_normal;
|
||||
if t < 0.0 {
|
||||
return None;
|
||||
}
|
||||
Some(IntersectionInfo {
|
||||
distance: t,
|
||||
location: ray.point_at(t),
|
||||
normal: self.normal,
|
||||
tangent: self.tangent,
|
||||
cotangent: self.cotangent,
|
||||
retro: -ray.direction,
|
||||
material: Arc::clone(&self.material),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl HasBoundingBox for Plane {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
let p0 = self.normal * self.distance_from_origin;
|
||||
let f = |v: Vec3<f64>| {
|
||||
Vec3::new(
|
||||
if v.x() == 0.0 { 0.0 } else { f64::INFINITY },
|
||||
if v.y() == 0.0 { 0.0 } else { f64::INFINITY },
|
||||
if v.z() == 0.0 { 0.0 } else { f64::INFINITY },
|
||||
)
|
||||
};
|
||||
let tangent = f(self.tangent);
|
||||
let cotangent = f(self.cotangent);
|
||||
let p1 = p0 + tangent;
|
||||
let p2 = p0 - tangent;
|
||||
let p3 = p0 + cotangent;
|
||||
let p4 = p0 - cotangent;
|
||||
BoundingBox::from_points(&[p1, p2, p3, p4])
|
||||
}
|
||||
}
|
||||
|
||||
impl Primitive for Plane {
|
||||
fn transform(&self, transformation: &crate::math::Affine3<f64>) -> Box<dyn Primitive> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::materials::LambertianMaterial;
|
||||
use crate::math::Vec3;
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_plane() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(-1.0, 0.0, 1.0));
|
||||
let p = Plane::new(
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
-5.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
if let None = p.intersect(&r) {
|
||||
panic!("Intersection failed.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_does_not_intersect_plane() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(1.0, 0.0, 1.0));
|
||||
let p = Plane::new(
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
-5.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
if let Some(_) = p.intersect(&r) {
|
||||
panic!("Intersection failed.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_point_is_on_plane() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(-1.0, 0.0, 1.0));
|
||||
let p = Plane::new(
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
-5.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
match p.intersect(&r) {
|
||||
Some(IntersectionInfo {
|
||||
distance: _,
|
||||
location,
|
||||
normal: _,
|
||||
tangent: _,
|
||||
cotangent: _,
|
||||
retro: _,
|
||||
material: _,
|
||||
}) => assert!((location.x() - (-5.0f64)).abs() < 0.0000000001),
|
||||
None => panic!(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box_is_correct_for_yz_plane() {
|
||||
let target = Plane::new(
|
||||
Vec3::new(1.0, 0.0, 0.0),
|
||||
2.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bb = target.bounding_box();
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2.0, 2000.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2.0, 0.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2.0, -2000.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2.0, 2.0, 3000.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2.0, 2.0, 0.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2.0, 2.0, -3000.0)));
|
||||
assert!(!bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box_is_correct_for_yz_plane_with_negative_normal() {
|
||||
let target = Plane::new(
|
||||
Vec3::new(-1.0, 0.0, 0.0),
|
||||
2.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bb = target.bounding_box();
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2.0, 2000.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2.0, 0.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2.0, -2000.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, 3000.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, 0.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2.0, 2.0, -3000.0)));
|
||||
assert!(!bb.contains_point(Vec3::new(-3.0, 2.0, 3.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box_is_correct_for_xz_plane() {
|
||||
let target = Plane::new(
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
2.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bb = target.bounding_box();
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, 1.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1000.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-1000.0, 2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 3000.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 0.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, -3000.0)));
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, 3.0, 3.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box_is_correct_for_xz_plane_with_negative_normal() {
|
||||
let target = Plane::new(
|
||||
Vec3::new(0.0, -1.0, 0.0),
|
||||
2.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bb = target.bounding_box();
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, -1.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, -2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1000.0, -2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(0.0, -2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-1000.0, -2.0, 3.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, -2.0, 3000.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, -2.0, 0.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, -2.0, -3000.0)));
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, 3.0, 3.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box_is_correct_for_xy_plane() {
|
||||
let target = Plane::new(
|
||||
Vec3::new(0.0, 0.0, 1.0),
|
||||
2.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bb = target.bounding_box();
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 1.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2000.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 0.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, -2000.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2000.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2000.0, 2.0, 2.0)));
|
||||
assert!(!bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box_is_correct_for_xy_plane_with_negative_normal() {
|
||||
let target = Plane::new(
|
||||
Vec3::new(0.0, 0.0, -1.0),
|
||||
-2.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bb = target.bounding_box();
|
||||
assert!(!bb.contains_point(Vec3::new(1.0, 2.0, 1.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2000.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 0.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, -2000.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2000.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2000.0, 2.0, 2.0)));
|
||||
assert!(!bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn bounding_box_is_infinite_when_normal_is_not_aligned_with_axis() {
|
||||
let target = Plane::new(
|
||||
Vec3::new(0.1, 0.0, -1.0),
|
||||
-2.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bb = target.bounding_box();
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 1.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 2000.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, 0.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(1.0, -2000.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(2000.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(0.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(-2000.0, 2.0, 2.0)));
|
||||
assert!(bb.contains_point(Vec3::new(3.0, 2.0, 3.0)));
|
||||
}
|
||||
}
|
||||
|
|
@ -1,265 +0,0 @@
|
|||
use crate::materials::Material;
|
||||
use crate::math::Vec3;
|
||||
|
||||
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectionInfo, Primitive, Ray};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Sphere {
|
||||
centre: Vec3<f64>,
|
||||
radius: f64,
|
||||
material: Arc<dyn Material>,
|
||||
}
|
||||
|
||||
impl Sphere {
|
||||
pub fn new(centre: Vec3<f64>, radius: f64, material: Arc<dyn Material>) -> Sphere {
|
||||
Sphere {
|
||||
centre,
|
||||
radius,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*impl Transform for Sphere {
|
||||
fn transform(&self, transformation: &Affine3<f64>) -> Self {
|
||||
Sphere {
|
||||
centre: transformation.transform_point(&self.centre),
|
||||
// This is not the most efficient way of calculating the radius,
|
||||
//but will work as long as the resulting shape is still a sphere.
|
||||
radius: transformation
|
||||
.transform_vector(&Vector3::new(self.radius, 0.0, 0.0))
|
||||
.norm(),
|
||||
material: Arc::clone(&self.material),
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl Intersect for Sphere {
|
||||
fn intersect<'a>(&'_ self, ray: &Ray) -> Option<IntersectionInfo> {
|
||||
let r_o = ray.origin;
|
||||
let centre_coords = self.centre;
|
||||
let a = ray
|
||||
.direction
|
||||
.component_mul(&ray.direction)
|
||||
.coords
|
||||
.iter()
|
||||
.fold(0.0, |a, b| a + *b);
|
||||
let b = ((r_o.component_mul(&ray.direction) - centre_coords.component_mul(&ray.direction))
|
||||
* 2.0)
|
||||
.coords
|
||||
.iter()
|
||||
.fold(0.0, |a, b| a + *b);
|
||||
let c = (r_o.component_mul(&r_o) + centre_coords.component_mul(¢re_coords)
|
||||
- centre_coords.component_mul(&r_o) * 2.0)
|
||||
.coords
|
||||
.iter()
|
||||
.fold(0.0, |a, b| a + *b)
|
||||
- self.radius * self.radius;
|
||||
let delta_squared = b * b - 4.0 * a * c;
|
||||
if delta_squared < 0.0 {
|
||||
None
|
||||
} else {
|
||||
let delta = delta_squared.sqrt();
|
||||
let one_over_2_a = 1.0 / (2.0 * a);
|
||||
let t1 = (-b - delta) * one_over_2_a;
|
||||
let t2 = (-b + delta) * one_over_2_a;
|
||||
let distance = if t1 < 0.0 || (t2 >= 0.0 && t1 >= t2) {
|
||||
t2
|
||||
} else {
|
||||
t1
|
||||
};
|
||||
if distance <= 0.0 {
|
||||
None
|
||||
} else {
|
||||
let location = ray.point_at(distance);
|
||||
let normal = (location - self.centre).normalize();
|
||||
let tangent = normal.cross(&Vec3::unit_z()).normalize();
|
||||
let cotangent = normal.cross(&tangent);
|
||||
let retro = -ray.direction;
|
||||
Some(IntersectionInfo {
|
||||
distance,
|
||||
location,
|
||||
normal,
|
||||
tangent,
|
||||
cotangent,
|
||||
retro,
|
||||
material: Arc::clone(&self.material),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasBoundingBox for Sphere {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
let radius_xyz = Vec3::new(self.radius, self.radius, self.radius);
|
||||
BoundingBox::from_corners(self.centre + radius_xyz, self.centre - radius_xyz)
|
||||
}
|
||||
}
|
||||
|
||||
impl Primitive for Sphere {
|
||||
fn transform(&self, transformation: &crate::math::Affine3<f64>) -> Box<dyn Primitive> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use quickcheck::TestResult;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
use super::*;
|
||||
use crate::materials::LambertianMaterial;
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_sphere() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vec3::new(1.5, 1.5, 15.0),
|
||||
5.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
if let None = s.intersect(&r) {
|
||||
panic!("Intersection failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_does_not_intersect_sphere_when_sphere_is_in_front() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vec3::new(-5.0, 1.5, 15.0),
|
||||
5.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
if let Some(_) = s.intersect(&r) {
|
||||
panic!("Intersection passed.");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_does_not_intersect_sphere_when_sphere_is_behind() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vec3::new(1.5, 1.5, -15.0),
|
||||
5.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
if let Some(_) = s.intersect(&r) {
|
||||
panic!("Intersection failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ray_intersects_sphere_when_origin_is_inside() {
|
||||
let r = Ray::new(Vec3::new(1.0, 2.0, 3.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
let s = Sphere::new(
|
||||
Vec3::new(1.5, 1.5, 2.0),
|
||||
5.0,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
if let None = s.intersect(&r) {
|
||||
panic!("Intersection failed");
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn ray_intersects_sphere_centre_at_correct_distance(
|
||||
ray_origin: Vec3<f64>,
|
||||
sphere_centre: Vec3<f64>,
|
||||
radius: f64,
|
||||
) -> TestResult {
|
||||
if radius <= 0.0 || radius + 0.000001 >= (ray_origin - sphere_centre).norm() {
|
||||
return TestResult::discard();
|
||||
};
|
||||
let sphere = Sphere::new(
|
||||
sphere_centre,
|
||||
radius,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let ray = Ray::new(ray_origin, sphere_centre - ray_origin);
|
||||
let info = sphere.intersect(&ray).unwrap();
|
||||
let distance_to_centre = (sphere_centre - ray.origin).norm();
|
||||
TestResult::from_bool(
|
||||
(distance_to_centre - (info.distance + sphere.radius)).abs() < 0.00001,
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn all_points_on_sphere_are_in_bounding_box(sphere_centre: Vec3<f64>, radius_vector: Vec3<f64>) -> bool {
|
||||
let target_sphere = Sphere::new(
|
||||
sphere_centre,
|
||||
radius_vector.norm(),
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let bounding_box = target_sphere.bounding_box();
|
||||
bounding_box.contains_point(sphere_centre + radius_vector)
|
||||
}
|
||||
|
||||
/*#[quickcheck]
|
||||
fn translation_moves_centre(
|
||||
sphere_centre: Vec3<f64>,
|
||||
radius: f64,
|
||||
translation_vector: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
if radius <= 0.0 {
|
||||
return TestResult::discard();
|
||||
};
|
||||
let sphere = Sphere::new(
|
||||
sphere_centre,
|
||||
radius,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let expected_centre = sphere.centre + translation_vector;
|
||||
let mut transformation = Affine3::identity();
|
||||
transformation *= Translation3::from(translation_vector);
|
||||
let sphere = sphere.transform(&transformation);
|
||||
TestResult::from_bool(expected_centre == sphere.centre)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn translation_does_not_change_radius(
|
||||
sphere_centre: Vec3<f64>,
|
||||
radius: f64,
|
||||
translation_vector: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
if radius <= 0.0 {
|
||||
return TestResult::discard();
|
||||
};
|
||||
let sphere = Sphere::new(
|
||||
sphere_centre,
|
||||
radius,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let expected_radius = sphere.radius;
|
||||
let mut transformation = Affine3::identity();
|
||||
transformation *= Translation3::from(translation_vector);
|
||||
let sphere = sphere.transform(&transformation);
|
||||
TestResult::from_bool(expected_radius == sphere.radius)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn rotation_about_centre_does_not_move_centre(
|
||||
sphere_centre: Vec3<f64>,
|
||||
radius: f64,
|
||||
rotation_vector: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
if radius <= 0.0 {
|
||||
return TestResult::discard();
|
||||
};
|
||||
let sphere = Sphere::new(
|
||||
sphere_centre,
|
||||
radius,
|
||||
Arc::new(LambertianMaterial::new_dummy()),
|
||||
);
|
||||
let expected_centre = sphere.centre;
|
||||
let mut transformation = Affine3::identity();
|
||||
transformation *= Translation3::from(sphere.centre.coords)
|
||||
* Rotation3::new(rotation_vector)
|
||||
* Translation3::from(-sphere.centre.coords);
|
||||
let sphere = sphere.transform(&transformation);
|
||||
TestResult::from_bool((expected_centre - sphere.centre).norm() < 0.000000001)
|
||||
}*/
|
||||
}
|
||||
|
|
@ -1,922 +0,0 @@
|
|||
use crate::materials::Material;
|
||||
use crate::math::{Vec2, Vec3};
|
||||
|
||||
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectionInfo, Primitive, Ray};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Triangle {
|
||||
pub vertices: [Vec3<f64>; 3],
|
||||
pub normals: [Vec3<f64>; 3],
|
||||
pub material: Arc<dyn Material>,
|
||||
}
|
||||
|
||||
/*impl Transform for Triangle {
|
||||
fn transform(&self, transformation: &Affine3<f64>) -> Self {
|
||||
let normal_transformation =
|
||||
Affine3::from_matrix_unchecked(transformation.inverse().matrix().transpose());
|
||||
Triangle {
|
||||
vertices: [
|
||||
transformation.transform_point(&self.vertices[0]),
|
||||
transformation.transform_point(&self.vertices[1]),
|
||||
transformation.transform_point(&self.vertices[2]),
|
||||
],
|
||||
normals: [
|
||||
normal_transformation.transform_vector(&self.normals[0]),
|
||||
normal_transformation.transform_vector(&self.normals[1]),
|
||||
normal_transformation.transform_vector(&self.normals[2]),
|
||||
],
|
||||
material: Arc::clone(&self.material),
|
||||
}
|
||||
}
|
||||
}*/
|
||||
|
||||
impl Intersect for Triangle {
|
||||
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo> {
|
||||
let translation = -ray.origin;
|
||||
let indices = indices_with_index_of_largest_element_last(&ray.direction);
|
||||
let permuted_ray_direction = permute_vector_elements(&ray.direction, &indices);
|
||||
let shear_slopes = calculate_shear_to_z_axis(&permuted_ray_direction);
|
||||
let transformed_vertices: Vec<Vec3<f64>> = self
|
||||
.vertices
|
||||
.iter()
|
||||
.map(|elem| {
|
||||
apply_shear_to_z_axis(
|
||||
&permute_vector_elements(&(elem + translation), &indices),
|
||||
&shear_slopes,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let edge_functions = signed_edge_functions(&transformed_vertices);
|
||||
if edge_functions.coords.iter().all(|e| e.is_sign_positive())
|
||||
|| edge_functions.coords.iter().all(|e| e.is_sign_negative())
|
||||
{
|
||||
let barycentric_coordinates =
|
||||
barycentric_coordinates_from_signed_edge_functions(edge_functions.abs());
|
||||
let transformed_z = barycentric_coordinates
|
||||
.coords
|
||||
.iter()
|
||||
.zip(transformed_vertices.iter())
|
||||
.map(|(&coord, vertex)| vertex.z() * coord)
|
||||
.fold(0.0, |acc, z| acc + z);
|
||||
if transformed_z.is_sign_positive() != permuted_ray_direction.z().is_sign_positive() {
|
||||
return None;
|
||||
}
|
||||
let location = barycentric_coordinates
|
||||
.coords
|
||||
.iter()
|
||||
.zip(self.vertices.iter())
|
||||
.map(|(&barycentric_coord, vertex)| vertex * barycentric_coord)
|
||||
.fold(Vec3::zeros(), |a, e| a + e);
|
||||
let distance = (ray.origin - location).norm();
|
||||
let normal: Vec3<f64> = barycentric_coordinates
|
||||
.coords
|
||||
.iter()
|
||||
.zip(self.normals.iter())
|
||||
.fold(Vec3::zeros(), |acc, (&coord, vertex)| acc + vertex * coord)
|
||||
.normalize();
|
||||
let cotangent = (self.vertices[0] - self.vertices[1])
|
||||
.cross(&normal)
|
||||
.normalize();
|
||||
let tangent = cotangent.cross(&normal).normalize();
|
||||
let retro = (ray.origin - location).normalize();
|
||||
let material = Arc::clone(&self.material);
|
||||
Some(IntersectionInfo {
|
||||
distance,
|
||||
location,
|
||||
normal,
|
||||
tangent,
|
||||
cotangent,
|
||||
retro,
|
||||
material,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl HasBoundingBox for Triangle {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
BoundingBox::from_points(&self.vertices)
|
||||
}
|
||||
}
|
||||
|
||||
impl Primitive for Triangle {
|
||||
fn transform(&self, transformation: &crate::math::Affine3<f64>) -> Box<dyn Primitive> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
fn indices_with_index_of_largest_element_last(v: &Vec3<f64>) -> [usize; 3] {
|
||||
#[allow(clippy::collapsible_else_if)]
|
||||
if v.x() > v.y() {
|
||||
if v.z() > v.x() {
|
||||
[0, 1, 2]
|
||||
} else {
|
||||
[1, 2, 0]
|
||||
}
|
||||
} else {
|
||||
if v.z() > v.y() {
|
||||
[0, 1, 2]
|
||||
} else {
|
||||
[2, 0, 1]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_valid_permutation(indices: &[usize; 3]) -> bool {
|
||||
(0..2).all(|i: usize| indices.contains(&i))
|
||||
}
|
||||
|
||||
fn permute_vector_elements(v: &Vec3<f64>, indices: &[usize; 3]) -> Vec3<f64> {
|
||||
debug_assert!(is_valid_permutation(indices));
|
||||
Vec3::new(v[indices[0]], v[indices[1]], v[indices[2]])
|
||||
}
|
||||
|
||||
fn calculate_shear_to_z_axis(v: &Vec3<f64>) -> Vec2<f64> {
|
||||
Vec2::new(-v.x() / v.z(), -v.y() / v.z())
|
||||
}
|
||||
|
||||
fn apply_shear_to_z_axis(v: &Vec3<f64>, s: &Vec2<f64>) -> Vec3<f64> {
|
||||
Vec3::new(v.x() + s.x() * v.z(), v.y() + s.y() * v.z(), v.z())
|
||||
}
|
||||
|
||||
fn signed_edge_function(a: &Vec3<f64>, b: &Vec3<f64>) -> f64 {
|
||||
a.x() * b.y() - b.x() * a.y()
|
||||
}
|
||||
|
||||
fn signed_edge_functions(vertices: &[Vec3<f64>]) -> Vec3<f64> {
|
||||
// Iterate over the inputs in such a way that each output element is calculated
|
||||
// from the twoother elements of the input. ( (y,z) -> x, (z,x) -> y, (x,y) -> z )
|
||||
let coords: Vec<_> = vertices
|
||||
.iter()
|
||||
.cycle()
|
||||
.skip(1)
|
||||
.zip(vertices.iter().cycle().skip(2))
|
||||
.take(vertices.len())
|
||||
.map(|(v1, v2)| signed_edge_function(v1, v2))
|
||||
.collect();
|
||||
|
||||
Vec3::new(coords[0], coords[1], coords[2])
|
||||
}
|
||||
|
||||
fn barycentric_coordinates_from_signed_edge_functions(e: Vec3<f64>) -> Vec3<f64> {
|
||||
e * (1.0 / e.coords.iter().fold(0.0, |a, &b| a + b))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
/*mod triangle_transform {
|
||||
use super::*;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
use crate::materials::LambertianMaterial;
|
||||
|
||||
#[quickcheck]
|
||||
fn transform_by_identity_does_not_change_values(
|
||||
v0: Vec3,
|
||||
v1: Vec3,
|
||||
v2: Vec3,
|
||||
n0: Vec3,
|
||||
n1: Vec3,
|
||||
n2: Vec3,
|
||||
) -> bool {
|
||||
let n0 = n0.normalize();
|
||||
let n1 = n1.normalize();
|
||||
let n2 = n2.normalize();
|
||||
let target = Triangle {
|
||||
vertices: [v0, v1, v2],
|
||||
normals: [n0, n1, n2],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let target = target.transform(&Affine3::identity());
|
||||
target.vertices[0] == v0
|
||||
&& target.vertices[1] == v1
|
||||
&& target.vertices[2] == v2
|
||||
&& target.normals[0] == n0
|
||||
&& target.normals[1] == n1
|
||||
&& target.normals[2] == n2
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn translate_does_not_change_normals(
|
||||
v0: Vec3,
|
||||
v1: Vec3,
|
||||
v2: Vec3,
|
||||
n0: Vec3,
|
||||
n1: Vec3,
|
||||
n2: Vec3,
|
||||
translation: Vec3,
|
||||
) -> bool {
|
||||
let n0 = n0.normalize();
|
||||
let n1 = n1.normalize();
|
||||
let n2 = n2.normalize();
|
||||
let target = Triangle {
|
||||
vertices: [v0, v1, v2],
|
||||
normals: [n0, n1, n2],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let transformation = Affine3::identity() * Translation3::from(translation);
|
||||
let target = target.transform(&transformation);
|
||||
target.normals[0] == n0 && target.normals[1] == n1 && target.normals[2] == n2
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn translate_translates_vertices(
|
||||
v0: Vec3,
|
||||
v1: Vec3,
|
||||
v2: Vec3,
|
||||
n0: Vec3,
|
||||
n1: Vec3,
|
||||
n2: Vec3,
|
||||
translation: Vec3,
|
||||
) -> bool {
|
||||
let n0 = n0.normalize();
|
||||
let n1 = n1.normalize();
|
||||
let n2 = n2.normalize();
|
||||
let target = Triangle {
|
||||
vertices: [v0, v1, v2],
|
||||
normals: [n0, n1, n2],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let transformation = Affine3::identity() * Translation3::from(translation);
|
||||
let target = target.transform(&transformation);
|
||||
target.vertices[0] == v0 + translation
|
||||
&& target.vertices[1] == v1 + translation
|
||||
&& target.vertices[2] == v2 + translation
|
||||
}
|
||||
}*/
|
||||
|
||||
mod index_of_largest_element {
|
||||
use super::*;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[quickcheck]
|
||||
fn result_is_valid_permutation(v: Vec3<f64>) -> bool {
|
||||
let indices = indices_with_index_of_largest_element_last(&v);
|
||||
is_valid_permutation(&indices)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn result_includes_x(v: Vec3<f64>) -> bool {
|
||||
let indices = indices_with_index_of_largest_element_last(&v);
|
||||
indices.iter().any(|&i| i == 0)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn result_includes_y(v: Vec3<f64>) -> bool {
|
||||
let indices = indices_with_index_of_largest_element_last(&v);
|
||||
indices.iter().any(|&i| i == 1)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn result_includes_z(v: Vec3<f64>) -> bool {
|
||||
let indices = indices_with_index_of_largest_element_last(&v);
|
||||
indices.iter().any(|&i| i == 2)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn last_index_is_greater_than_or_equal_to_x(v: Vec3<f64>) -> bool {
|
||||
let indices = indices_with_index_of_largest_element_last(&v);
|
||||
v[indices[2]] >= v.x()
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn last_index_is_greater_than_or_equal_to_y(v: Vec3<f64>) -> bool {
|
||||
let indices = indices_with_index_of_largest_element_last(&v);
|
||||
v[indices[2]] >= v.y()
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn last_index_is_greater_than_or_equal_to_z(v: Vec3<f64>) -> bool {
|
||||
let indices = indices_with_index_of_largest_element_last(&v);
|
||||
v[indices[2]] >= v.z()
|
||||
}
|
||||
}
|
||||
|
||||
mod permute_vector_elements {
|
||||
use super::*;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[quickcheck]
|
||||
fn last_index_is_greater_than_or_equal_to_x(v: Vec3<f64>) -> bool {
|
||||
let p = permute_vector_elements(&v, &indices_with_index_of_largest_element_last(&v));
|
||||
p.z() >= v.x()
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn last_index_is_greater_than_or_equal_to_y(v: Vec3<f64>) -> bool {
|
||||
let p = permute_vector_elements(&v, &indices_with_index_of_largest_element_last(&v));
|
||||
p.z() >= v.y()
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn last_index_is_greater_than_or_equal_to_z(v: Vec3<f64>) -> bool {
|
||||
let p = permute_vector_elements(&v, &indices_with_index_of_largest_element_last(&v));
|
||||
p.z() >= v.z()
|
||||
}
|
||||
}
|
||||
|
||||
mod shear_to_z_axis {
|
||||
use super::*;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[quickcheck]
|
||||
fn shear_to_z_axis_makes_x_zero(v: Vec3<f64>) -> bool {
|
||||
let s = calculate_shear_to_z_axis(&v);
|
||||
apply_shear_to_z_axis(&v, &s).x().abs() < 0.00001
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn shear_to_z_axis_makes_y_zero(v: Vec3<f64>) -> bool {
|
||||
let s = calculate_shear_to_z_axis(&v);
|
||||
apply_shear_to_z_axis(&v, &s).y().abs() < 0.00001
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn shear_to_z_axis_leaves_z_unchanged(v: Vec3<f64>) -> bool {
|
||||
let s = calculate_shear_to_z_axis(&v);
|
||||
apply_shear_to_z_axis(&v, &s).z() == v.z()
|
||||
}
|
||||
}
|
||||
|
||||
mod barycentric_coordinates {
|
||||
use super::*;
|
||||
use quickcheck::TestResult;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[quickcheck]
|
||||
fn sign_of_signed_edge_function_matches_winding(a: Vec3<f64>, b: Vec3<f64>) -> TestResult {
|
||||
let a_2d = Vec2::new(a.x(), a.y());
|
||||
let b_2d = Vec2::new(b.x(), b.y());
|
||||
let c_2d = Vec2::new(0.0, 0.0);
|
||||
let winding = (b_2d - a_2d).perp(&(c_2d - b_2d));
|
||||
if winding.abs() < 0.00001 {
|
||||
TestResult::discard()
|
||||
} else {
|
||||
let winding = winding.is_sign_positive();
|
||||
let area_sign = signed_edge_function(&a, &b).is_sign_positive();
|
||||
TestResult::from_bool(winding == area_sign)
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn signed_edge_functions_has_same_result_as_signed_edge_function(
|
||||
a: Vec3<f64>,
|
||||
b: Vec3<f64>,
|
||||
c: Vec3<f64>,
|
||||
) -> bool {
|
||||
let es = signed_edge_functions(&vec![a, b, c]);
|
||||
es[0] == signed_edge_function(&b, &c)
|
||||
&& es[1] == signed_edge_function(&c, &a)
|
||||
&& es[2] == signed_edge_function(&a, &b)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn barycentric_coordinates_sum_to_one(a: Vec3<f64>, b: Vec3<f64>, c: Vec3<f64>) -> bool {
|
||||
let barycentric_coordinates =
|
||||
barycentric_coordinates_from_signed_edge_functions(signed_edge_functions(&vec![
|
||||
a, b, c,
|
||||
]));
|
||||
(barycentric_coordinates
|
||||
.coords
|
||||
.iter()
|
||||
.fold(0.0, |a, b| a + b)
|
||||
- 1.0)
|
||||
.abs()
|
||||
< 0.00000001
|
||||
}
|
||||
}
|
||||
|
||||
mod triangle_intersect {
|
||||
use super::*;
|
||||
use crate::materials::LambertianMaterial;
|
||||
use quickcheck::{Arbitrary, TestResult};
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[test]
|
||||
fn intersection_passes_with_ray_along_z_axis_ccw_winding() {
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(1.0, -1.0, 1.0),
|
||||
Vec3::new(-1.0, -1.0, 1.0),
|
||||
],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let target_ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
if let None = target_triangle.intersect(&target_ray) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_passes_with_ray_along_z_axis_cw_winding() {
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::new(0.0, 1.0, 1.0),
|
||||
Vec3::new(-1.0, -1.0, 1.0),
|
||||
Vec3::new(1.0, -1.0, 1.0),
|
||||
],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let target_ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
if let None = target_triangle.intersect(&target_ray) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_passes_with_ray_along_nagative_z_axis_ccw_winding() {
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::new(0.0, 1.0, -1.0),
|
||||
Vec3::new(1.0, -1.0, -1.0),
|
||||
Vec3::new(-1.0, -1.0, -1.0),
|
||||
],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let target_ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, -1.0));
|
||||
if let None = target_triangle.intersect(&target_ray) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_passes_with_ray_along_negativez_axis_cw_winding() {
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::new(0.0, 1.0, -1.0),
|
||||
Vec3::new(-1.0, -1.0, -1.0),
|
||||
Vec3::new(1.0, -1.0, -1.0),
|
||||
],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let target_ray = Ray::new(Vec3::new(0.0, 0.0, 0.0), Vec3::new(0.0, 0.0, -1.0));
|
||||
if let None = target_triangle.intersect(&target_ray) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_passes_with_ray_along_z_axis_but_translated_ccw_winding() {
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::new(5.0, 6.0, 6.0),
|
||||
Vec3::new(6.0, 4.0, 6.0),
|
||||
Vec3::new(4.0, 4.0, 6.0),
|
||||
],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let target_ray = Ray::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(0.0, 0.0, 1.0));
|
||||
if let None = target_triangle.intersect(&target_ray) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_passes_with_ray_at_angle_to_z_axisand_translated_ccw_winding() {
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::new(6.0, 6.5, 6.0),
|
||||
Vec3::new(7.0, 4.5, 6.0),
|
||||
Vec3::new(5.0, 4.5, 6.0),
|
||||
],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let target_ray = Ray::new(Vec3::new(5.0, 5.0, 5.0), Vec3::new(1.0, 0.5, 1.0));
|
||||
if let None = target_triangle.intersect(&target_ray) {
|
||||
panic!()
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_with_centroid_and_test_result<
|
||||
F: Fn(Option<IntersectionInfo>, Vec3<f64>) -> bool,
|
||||
>(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
test: F,
|
||||
) -> TestResult {
|
||||
let centroid: Vec3<f64> = [vertex0, vertex1, vertex2]
|
||||
.iter()
|
||||
.fold(Vec3::new(0.0, 0.0, 0.0), |acc, &elem| acc + elem)
|
||||
* (1.0 / 3.0);
|
||||
let ray_direction = (centroid - ray_origin).normalize();
|
||||
let normal = (vertex1 - vertex0).cross(&(vertex2 - vertex0)).normalize();
|
||||
if normal.dot(&ray_direction).abs() < 0.000_000_1 {
|
||||
//Discard if triangle is too close to edge-on
|
||||
return TestResult::discard();
|
||||
}
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::from(vertex0),
|
||||
Vec3::from(vertex1),
|
||||
Vec3::from(vertex2),
|
||||
],
|
||||
normals: [normal; 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let ray = Ray::new(ray_origin, ray_direction);
|
||||
|
||||
TestResult::from_bool(test(target_triangle.intersect(&ray), centroid))
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_with_centroid_hits(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
let centroid: Vec3<f64> = [vertex0, vertex1, vertex2]
|
||||
.iter()
|
||||
.fold(Vec3::new(0.0, 0.0, 0.0), |acc, &elem| acc + elem)
|
||||
* (1.0 / 3.0);
|
||||
let ray_direction = (centroid - ray_origin).normalize();
|
||||
let normal = (vertex1 - vertex0).cross(&(vertex2 - vertex0)).normalize();
|
||||
if normal.dot(&ray_direction).abs() < 0.000_000_1 {
|
||||
//Discard if triangle is too close to edge-on
|
||||
return TestResult::discard();
|
||||
}
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::from(vertex0),
|
||||
Vec3::from(vertex1),
|
||||
Vec3::from(vertex2),
|
||||
],
|
||||
normals: [normal; 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let ray = Ray::new(ray_origin, ray_direction);
|
||||
|
||||
if let Some(_) = target_triangle.intersect(&ray) {
|
||||
TestResult::passed()
|
||||
} else {
|
||||
TestResult::failed()
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_with_centroid_hits_centroid(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
intersect_with_centroid_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
|result, centroid| {
|
||||
if let Some(IntersectionInfo { location, .. }) = result {
|
||||
(location - centroid).norm() < 0.000_000_1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_with_centroid_hits_at_expected_distance(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
intersect_with_centroid_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
|result, centroid| {
|
||||
if let Some(IntersectionInfo { distance, .. }) = result {
|
||||
((ray_origin - centroid).norm() - distance).abs() < 0.000_000_1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_with_centroid_has_expected_normal(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
intersect_with_centroid_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
|result, _| {
|
||||
if let Some(IntersectionInfo { normal, .. }) = result {
|
||||
(normal - (vertex1 - vertex0).cross(&(vertex2 - vertex0)).normalize())
|
||||
.norm()
|
||||
< 0.000_000_1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_with_centroid_has_expected_retro(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
) -> TestResult {
|
||||
intersect_with_centroid_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
|result, centroid| {
|
||||
let expected_retro = (ray_origin - centroid).normalize();
|
||||
if let Some(IntersectionInfo { retro, .. }) = result {
|
||||
(expected_retro - retro).norm() < 0.000_000_1
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
struct BarycentricCoords {
|
||||
alpha: f64,
|
||||
beta: f64,
|
||||
gamma: f64,
|
||||
}
|
||||
|
||||
impl quickcheck::Arbitrary for BarycentricCoords {
|
||||
fn arbitrary<G: quickcheck::Gen>(g: &mut G) -> Self {
|
||||
let e = 0.000_000_1;
|
||||
let alpha = <f64 as Arbitrary>::arbitrary(g).abs().fract() * (1.0 - e) + e;
|
||||
let beta = <f64 as Arbitrary>::arbitrary(g).abs().fract() * (1.0 - alpha) + e;
|
||||
let gamma = 1.0 - (alpha + beta);
|
||||
BarycentricCoords { alpha, beta, gamma }
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_with_barycentric_and_test_result<
|
||||
F: Fn(Option<IntersectionInfo>, Vec3<f64>) -> bool,
|
||||
>(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
barycentric_coords: BarycentricCoords,
|
||||
test: F,
|
||||
) -> TestResult {
|
||||
let point = vertex0 * barycentric_coords.alpha
|
||||
+ vertex1 * barycentric_coords.beta
|
||||
+ vertex2 * barycentric_coords.gamma;
|
||||
let ray_direction = (point - ray_origin).normalize();
|
||||
let normal = (vertex1 - vertex0).cross(&(vertex2 - vertex0)).normalize();
|
||||
if normal.dot(&ray_direction).abs() < 0.000_000_1 {
|
||||
//Discard if triangle is too close to edge-on
|
||||
return TestResult::discard();
|
||||
}
|
||||
let target_triangle = Triangle {
|
||||
vertices: [
|
||||
Vec3::from(vertex0),
|
||||
Vec3::from(vertex1),
|
||||
Vec3::from(vertex2),
|
||||
],
|
||||
normals: [normal; 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
let ray = Ray::new(ray_origin, ray_direction);
|
||||
|
||||
TestResult::from_bool(test(target_triangle.intersect(&ray), point))
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn point_with_arbitrary_barycentric_coords_hits(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
barycentric_coords: BarycentricCoords,
|
||||
) -> TestResult {
|
||||
intersect_with_barycentric_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
barycentric_coords,
|
||||
|result, _point| {
|
||||
if let Some(_) = result {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn point_with_arbitrary_barycentric_coords_has_expected_normal(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
barycentric_coords: BarycentricCoords,
|
||||
) -> TestResult {
|
||||
intersect_with_barycentric_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
barycentric_coords,
|
||||
|result, _point| {
|
||||
let expected_normal =
|
||||
(vertex1 - vertex0).cross(&(vertex2 - vertex0)).normalize();
|
||||
if let Some(IntersectionInfo { normal, .. }) = result {
|
||||
(normal - expected_normal).norm().abs() < 0.000_01
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn point_with_arbitrary_barycentric_coords_has_expected_distance(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
barycentric_coords: BarycentricCoords,
|
||||
) -> TestResult {
|
||||
intersect_with_barycentric_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
barycentric_coords,
|
||||
|result, point| {
|
||||
let expected_distance = (point - ray_origin).norm();
|
||||
if let Some(IntersectionInfo { distance, .. }) = result {
|
||||
(distance - expected_distance).abs() < 0.000_01
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn point_with_arbitrary_barycentric_coords_has_expected_retro(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
barycentric_coords: BarycentricCoords,
|
||||
) -> TestResult {
|
||||
intersect_with_barycentric_and_test_result(
|
||||
vertex0,
|
||||
vertex1,
|
||||
vertex2,
|
||||
ray_origin,
|
||||
barycentric_coords,
|
||||
|result, point| {
|
||||
let expected_retro = (ray_origin - point).normalize();
|
||||
if let Some(IntersectionInfo { retro, .. }) = result {
|
||||
(retro - expected_retro).norm().abs() < 0.000_01
|
||||
} else {
|
||||
false
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_fails_when_ray_outside_first_edge(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
uv: Vec2<f64>,
|
||||
) -> bool {
|
||||
let uv_origin = Vec3::from(vertex0);
|
||||
let u_axis = (vertex1 - vertex0).normalize();
|
||||
let w_axis = (vertex2 - vertex0).cross(&u_axis).normalize();
|
||||
let v_axis = w_axis.cross(&u_axis);
|
||||
let target_point = uv_origin + u_axis * uv.x() + v_axis * uv.y().abs();
|
||||
let ray = Ray {
|
||||
origin: ray_origin,
|
||||
direction: (target_point - ray_origin).normalize(),
|
||||
};
|
||||
let triangle = Triangle {
|
||||
vertices: [vertex0, vertex1, vertex2],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
match triangle.intersect(&ray) {
|
||||
Some(_) => false,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_fails_when_ray_outside_second_edge(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
uv: Vec2<f64>,
|
||||
) -> bool {
|
||||
let uv_origin = Vec3::from(vertex0);
|
||||
let u_axis = (vertex2 - vertex1).normalize();
|
||||
let w_axis = (vertex1 - vertex0).cross(&u_axis).normalize();
|
||||
let v_axis = w_axis.cross(&u_axis);
|
||||
let target_point = uv_origin + u_axis * uv.x() + v_axis * uv.y().abs();
|
||||
let ray = Ray {
|
||||
origin: ray_origin,
|
||||
direction: (target_point - ray_origin).normalize(),
|
||||
};
|
||||
let triangle = Triangle {
|
||||
vertices: [vertex0, vertex1, vertex2],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
match triangle.intersect(&ray) {
|
||||
Some(_) => false,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_fails_when_ray_outside_third_edge(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
uv: Vec2<f64>,
|
||||
) -> bool {
|
||||
let uv_origin = Vec3::from(vertex0);
|
||||
let u_axis = (vertex0 - vertex2).normalize();
|
||||
let w_axis = (vertex1 - vertex2).cross(&u_axis).normalize();
|
||||
let v_axis = w_axis.cross(&u_axis);
|
||||
let target_point = uv_origin + u_axis * uv.x() + v_axis * uv.y().abs();
|
||||
let ray = Ray {
|
||||
origin: ray_origin,
|
||||
direction: (target_point - ray_origin).normalize(),
|
||||
};
|
||||
let triangle = Triangle {
|
||||
vertices: [vertex0, vertex1, vertex2],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
match triangle.intersect(&ray) {
|
||||
Some(_) => false,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn intersection_fails_when_triangle_is_behind_ray(
|
||||
vertex0: Vec3<f64>,
|
||||
vertex1: Vec3<f64>,
|
||||
vertex2: Vec3<f64>,
|
||||
ray_origin: Vec3<f64>,
|
||||
barycentric_coords: BarycentricCoords,
|
||||
) -> bool {
|
||||
let point_behind_ray = vertex0 * barycentric_coords.alpha
|
||||
+ vertex1 * barycentric_coords.beta
|
||||
+ vertex2 * barycentric_coords.gamma;
|
||||
let ray = Ray {
|
||||
origin: ray_origin,
|
||||
direction: (ray_origin - point_behind_ray).normalize(),
|
||||
};
|
||||
let triangle = Triangle {
|
||||
vertices: [vertex0, vertex1, vertex2],
|
||||
normals: [Vec3::zeros(); 3],
|
||||
material: Arc::new(LambertianMaterial::new_dummy()),
|
||||
};
|
||||
match triangle.intersect(&ray) {
|
||||
Some(_) => false,
|
||||
None => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,29 +0,0 @@
|
|||
use super::{BoundingBox, HasBoundingBox, Intersect, IntersectionInfo, Primitive, Ray};
|
||||
use crate::math::Affine3;
|
||||
|
||||
impl HasBoundingBox for Vec<Box<dyn Primitive>> {
|
||||
fn bounding_box(&self) -> BoundingBox {
|
||||
self.iter().fold(BoundingBox::empty(), |acc, elem| {
|
||||
acc.union(&elem.bounding_box())
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl Intersect for Vec<Box<dyn Primitive>> {
|
||||
fn intersect(&self, ray: &Ray) -> Option<IntersectionInfo> {
|
||||
self.iter()
|
||||
.flat_map(|primitive| primitive.intersect(ray))
|
||||
.min_by(
|
||||
|a, b| match PartialOrd::partial_cmp(&a.distance, &b.distance) {
|
||||
None => std::cmp::Ordering::Less,
|
||||
Some(ordering) => ordering,
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Primitive for Vec<Box<dyn Primitive>> {
|
||||
fn transform(&self, transformation: &Affine3<f64>) -> Box<dyn Primitive> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,58 +0,0 @@
|
|||
pub trait NormalizedToU32 {
|
||||
fn normalized_to_u32(self, num_bits: usize) -> u32;
|
||||
}
|
||||
|
||||
impl NormalizedToU32 for f32 {
|
||||
fn normalized_to_u32(self, num_bits: usize) -> u32 {
|
||||
let scale = (num_bits as f32).exp2() - 1.0;
|
||||
(self * scale) as u32
|
||||
}
|
||||
}
|
||||
|
||||
impl NormalizedToU32 for f64 {
|
||||
fn normalized_to_u32(self, num_bits: usize) -> u32 {
|
||||
let scale = (num_bits as f64).exp2() - 1.0;
|
||||
(self * scale) as u32
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn zero_f32_normalizes_to_zero() {
|
||||
let target = 0.0f32;
|
||||
assert!(target.normalized_to_u32(10) == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_f32_normalizes_to_all_ones() {
|
||||
let target = 1.0f32;
|
||||
assert!(target.normalized_to_u32(10) == 0b1111111111);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn half_f32_normalizes_to_half_value() {
|
||||
let target = 0.5f32;
|
||||
assert!(target.normalized_to_u32(10) == 511);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zero_f64_normalizes_to_zero() {
|
||||
let target = 0.0f64;
|
||||
assert!(target.normalized_to_u32(10) == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_f64_normalizes_to_all_ones() {
|
||||
let target = 1.0f64;
|
||||
assert!(target.normalized_to_u32(10) == 0b1111111111);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn half_f64_normalizes_to_half_value() {
|
||||
let target = 0.5f64;
|
||||
assert!(target.normalized_to_u32(10) == 511);
|
||||
}
|
||||
}
|
||||
|
|
@ -1,16 +1,18 @@
|
|||
use super::raycasting::{IntersectionInfo, Ray};
|
||||
use super::scene::Scene;
|
||||
|
||||
pub struct Sampler<'a> {
|
||||
pub scene: &'a Scene,
|
||||
use nalgebra::RealField;
|
||||
|
||||
pub struct Sampler<'a, T: RealField> {
|
||||
pub scene: &'a Scene<T>,
|
||||
}
|
||||
|
||||
impl<'a> Sampler<'a> {
|
||||
pub fn sample(&self, ray: &Ray) -> Option<IntersectionInfo> {
|
||||
impl<'a, T: RealField> Sampler<'a, T> {
|
||||
pub fn sample(&self, ray: &Ray<T>) -> Option<IntersectionInfo<T>> {
|
||||
self.scene
|
||||
.objects
|
||||
.iter()
|
||||
.flat_map(|object| object.intersect(ray))
|
||||
.flat_map(|object| object.intersect(&ray))
|
||||
.min_by(
|
||||
|a, b| match PartialOrd::partial_cmp(&a.distance, &b.distance) {
|
||||
None => std::cmp::Ordering::Less,
|
||||
|
|
|
|||
10
src/scene.rs
10
src/scene.rs
|
|
@ -1,8 +1,8 @@
|
|||
use crate::math::Vec3;
|
||||
use nalgebra::{RealField, Vector3};
|
||||
|
||||
use crate::raycasting::Primitive;
|
||||
use crate::raycasting::Intersect;
|
||||
|
||||
pub struct Scene {
|
||||
pub camera_location: Vec3<f64>,
|
||||
pub objects: Vec<Box<dyn Primitive>>,
|
||||
pub struct Scene<T: RealField> {
|
||||
pub camera_location: Vector3<T>,
|
||||
pub objects: Vec<Box<dyn Intersect<T>>>,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,51 +0,0 @@
|
|||
use crate::math::{Mat3, Vec3};
|
||||
|
||||
pub fn try_change_of_basis_matrix(x: &Vec3<f64>, y: &Vec3<f64>, z: &Vec3<f64>) -> Option<Mat3<f64>> {
|
||||
Some(Mat3::from_rows(x, y, z))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[cfg(test)]
|
||||
mod change_of_basis_matrix {
|
||||
use super::*;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[test]
|
||||
fn produces_isentity_when_passed_axes() {
|
||||
let target: Mat3<f64> =
|
||||
try_change_of_basis_matrix(&Vec3::unit_x(), &Vec3::unit_y(), &Vec3::unit_z())
|
||||
.unwrap();
|
||||
assert!(target == Mat3::identity())
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn swap_xy_does_not_change_z(v: Vec3<f64>) {
|
||||
let target: Mat3<f64> =
|
||||
try_change_of_basis_matrix(&Vec3::unit_y(), &Vec3::unit_x(), &Vec3::unit_z())
|
||||
.unwrap();
|
||||
let v2 = target * v;
|
||||
assert!(v2.z() == v.z())
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn swap_xy_copies_y_to_x(v: Vec3<f64>) {
|
||||
let target: Mat3<f64> =
|
||||
try_change_of_basis_matrix(&Vec3::unit_y(), &Vec3::unit_x(), &Vec3::unit_z())
|
||||
.unwrap();
|
||||
let v2 = target * v;
|
||||
assert!(v2.x() == v.y())
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn swap_xy_copies_x_to_y(v: Vec3<f64>) {
|
||||
let target: Mat3<f64> =
|
||||
try_change_of_basis_matrix(&Vec3::unit_y(), &Vec3::unit_x(), &Vec3::unit_z())
|
||||
.unwrap();
|
||||
let v2 = target * v;
|
||||
assert!(v2.y() == v.x())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,130 +0,0 @@
|
|||
use std::ops::{Index, IndexMut};
|
||||
|
||||
/// 3D row-major dynamic array
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Array2D<T> {
|
||||
data: Vec<T>,
|
||||
height: usize,
|
||||
width: usize,
|
||||
}
|
||||
|
||||
impl<T: Default + Clone> Array2D<T> {
|
||||
/// Create Array2D with `width` and `height` filled with default values.
|
||||
pub fn new(height: usize, width: usize) -> Array2D<T> {
|
||||
Array2D {
|
||||
data: vec![Default::default(); width * height],
|
||||
height,
|
||||
width,
|
||||
}
|
||||
}
|
||||
|
||||
/// Reset contents of array to all default values
|
||||
pub fn clear(&mut self) {
|
||||
self.data.clear();
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Array2D<T> {
|
||||
pub fn get_height(&self) -> usize {
|
||||
self.height
|
||||
}
|
||||
|
||||
pub fn get_width(&self) -> usize {
|
||||
self.width
|
||||
}
|
||||
|
||||
/// Return single slice containing all elements (row-major)
|
||||
pub fn as_slice(&self) -> &[T] {
|
||||
self.data.as_slice()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Copy> Array2D<T> {
|
||||
/// Copy `source` into the Array2D at the specified location
|
||||
pub fn update_block(&mut self, start_row: usize, start_column: usize, source: &Array2D<T>) {
|
||||
let end_row = start_row + source.height;
|
||||
let end_column = start_column + source.width;
|
||||
assert!(end_row <= self.height);
|
||||
for i in 0..source.height {
|
||||
self[start_row + i][start_column..end_column].copy_from_slice(&source[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Index<usize> for Array2D<T> {
|
||||
type Output = [T];
|
||||
|
||||
fn index(&self, index: usize) -> &[T] {
|
||||
assert!(index < self.height);
|
||||
&self.data[(index * self.width)..((index + 1) * self.width)]
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> IndexMut<usize> for Array2D<T> {
|
||||
fn index_mut(&mut self, index: usize) -> &mut [T] {
|
||||
assert!(index < self.height);
|
||||
&mut self.data[(index * self.width)..((index + 1) * self.width)]
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn default_u8_is_all_zeros() {
|
||||
let target: Array2D<u8> = Array2D::new(10, 12);
|
||||
for i in 0..10 {
|
||||
for j in 0..12 {
|
||||
assert!(target[i][j] == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_if_row_outside_bounds() {
|
||||
let target: Array2D<u8> = Array2D::new(10, 12);
|
||||
assert!(target[10][6] == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn panics_if_column_outside_bounds() {
|
||||
let target: Array2D<u8> = Array2D::new(10, 12);
|
||||
assert!(target[5][12] == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn write_and_read_all_preserves_values() {
|
||||
let mut target: Array2D<u8> = Array2D::new(10, 12);
|
||||
for i in 0..10 {
|
||||
for j in 0..12 {
|
||||
target[i][j] = (i * 10 + j) as u8;
|
||||
}
|
||||
}
|
||||
for i in 0..10 {
|
||||
for j in 0..12 {
|
||||
assert!(target[i][j] == (i * 10 + j) as u8);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn update_block_writes_expected_values_in_block() {
|
||||
let mut target: Array2D<u8> = Array2D::new(10, 12);
|
||||
let mut source: Array2D<u8> = Array2D::new(2, 3);
|
||||
for i in 0..2 {
|
||||
for j in 0..3 {
|
||||
source[i][j] = (i * 3 + j) as u8;
|
||||
}
|
||||
}
|
||||
target.update_block(4, 5, &source);
|
||||
dbg!(&target);
|
||||
for i in 0..2 {
|
||||
for j in 0..3 {
|
||||
assert!(dbg!(target[4 + i][5 + j]) == dbg!(source[i][j]));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,240 +0,0 @@
|
|||
use crate::math::Vec3;
|
||||
use crate::util::Interval;
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct BoundingBox {
|
||||
pub bounds: [Interval; 3],
|
||||
}
|
||||
|
||||
impl BoundingBox {
|
||||
pub fn from_corners(a: Vec3<f64>, b: Vec3<f64>) -> Self {
|
||||
let mut result = BoundingBox {
|
||||
bounds: [Interval::infinite(); 3],
|
||||
};
|
||||
for (bounds_elem, a_elem, b_elem) in
|
||||
izip!(result.bounds.iter_mut(), a.coords.iter(), b.coords.iter())
|
||||
{
|
||||
*bounds_elem = Interval::new(*a_elem, *b_elem);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
BoundingBox {
|
||||
bounds: [Interval::empty(), Interval::empty(), Interval::empty()],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_point(p: Vec3<f64>) -> Self {
|
||||
BoundingBox {
|
||||
bounds: [
|
||||
Interval::degenerate(p.x()),
|
||||
Interval::degenerate(p.y()),
|
||||
Interval::degenerate(p.z()),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn from_points<'a, I>(points: I) -> Self
|
||||
where
|
||||
I: IntoIterator<Item = &'a Vec3<f64>>,
|
||||
{
|
||||
points
|
||||
.into_iter()
|
||||
.fold(BoundingBox::empty(), |acc, p| acc.expand_to_point(p))
|
||||
}
|
||||
|
||||
pub fn expand_to_point(&self, p: &Vec3<f64>) -> Self {
|
||||
BoundingBox {
|
||||
bounds: [
|
||||
self.bounds[0].expand_to_value(p.x()),
|
||||
self.bounds[1].expand_to_value(p.y()),
|
||||
self.bounds[2].expand_to_value(p.z()),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn contains_point(&self, p: Vec3<f64>) -> bool {
|
||||
self.bounds
|
||||
.iter()
|
||||
.zip(p.coords.iter())
|
||||
.all(|(interval, &value)| interval.contains_value(value))
|
||||
}
|
||||
|
||||
pub fn union(&self, other: &BoundingBox) -> BoundingBox {
|
||||
BoundingBox {
|
||||
bounds: [
|
||||
self.bounds[0].union(other.bounds[0]),
|
||||
self.bounds[1].union(other.bounds[1]),
|
||||
self.bounds[2].union(other.bounds[2]),
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
pub fn largest_dimension(&self) -> usize {
|
||||
let (dimension, _) = self
|
||||
.bounds
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(index, elem)| {
|
||||
(
|
||||
index,
|
||||
if elem.is_degenerate() {
|
||||
-1.0
|
||||
} else {
|
||||
elem.get_max() - elem.get_min()
|
||||
},
|
||||
)
|
||||
})
|
||||
.fold((0, 0.0), |(acc, acc_size), (elem, elem_size)| {
|
||||
if elem_size > acc_size {
|
||||
(elem, elem_size)
|
||||
} else {
|
||||
(acc, acc_size)
|
||||
}
|
||||
});
|
||||
dimension
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[test]
|
||||
fn from_corners_with_same_point_yields_degenerate_intervals() {
|
||||
let test_point = Vec3::new(0f64, 1f64, 2f64);
|
||||
let target = BoundingBox::from_corners(test_point, test_point);
|
||||
assert!(target.bounds.iter().all(|e| e.is_degenerate()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn from_corners_yields_same_result_with_any_oposite_corners() {
|
||||
let corner_000 = Vec3::new(0.0, 0.0, 0.0);
|
||||
let corner_001 = Vec3::new(0.0, 0.0, 1.0);
|
||||
let corner_010 = Vec3::new(0.0, 1.0, 0.0);
|
||||
let corner_011 = Vec3::new(0.0, 1.0, 1.0);
|
||||
let corner_100 = Vec3::new(1.0, 0.0, 0.0);
|
||||
let corner_101 = Vec3::new(1.0, 0.0, 1.0);
|
||||
let corner_110 = Vec3::new(1.0, 1.0, 0.0);
|
||||
let corner_111 = Vec3::new(1.0, 1.0, 1.0);
|
||||
|
||||
let test_inputs: Vec<(Vec3<f64>, Vec3<f64>)> = vec![
|
||||
(corner_000, corner_111),
|
||||
(corner_001, corner_110),
|
||||
(corner_010, corner_101),
|
||||
(corner_011, corner_100),
|
||||
(corner_100, corner_011),
|
||||
(corner_101, corner_010),
|
||||
(corner_110, corner_001),
|
||||
(corner_111, corner_000),
|
||||
];
|
||||
for (a, b) in test_inputs {
|
||||
let target = BoundingBox::from_corners(a, b);
|
||||
assert!(target
|
||||
.bounds
|
||||
.iter()
|
||||
.all(|bounds| bounds.get_min() == 0.0 && bounds.get_max() == 1.0));
|
||||
}
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn union_with_self_yields_self(a: Vec3<f64>, b: Vec3<f64>) -> bool {
|
||||
let target = BoundingBox::from_corners(a, b);
|
||||
let result = target.union(&target);
|
||||
target
|
||||
.bounds
|
||||
.iter()
|
||||
.zip(result.bounds.iter())
|
||||
.all(|(a, b)| a.get_min() == b.get_min() && a.get_max() == b.get_max())
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn union_yields_full_ranges(a: Vec3<f64>, b: Vec3<f64>, c: Vec3<f64>, d: Vec3<f64>) -> bool {
|
||||
let target1 = BoundingBox::from_corners(a, b);
|
||||
let target2 = BoundingBox::from_corners(c, d);
|
||||
let result = target1.union(&target2);
|
||||
izip!(
|
||||
result.bounds.iter(),
|
||||
target1.bounds.iter(),
|
||||
target2.bounds.iter()
|
||||
)
|
||||
.all(|(r, t1, t2)| {
|
||||
r.get_min() <= t1.get_min()
|
||||
&& r.get_min() <= t2.get_min()
|
||||
&& r.get_max() >= t1.get_max()
|
||||
&& r.get_max() >= t2.get_max()
|
||||
})
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn empty_box_contains_no_points(p: Vec3<f64>) -> bool {
|
||||
let target = BoundingBox::empty();
|
||||
!target.contains_point(p)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn from_points_produces_box_that_contains_only_points_bounded_by_inputs_on_all_axes(
|
||||
p: Vec3<f64>,
|
||||
a: Vec3<f64>,
|
||||
b: Vec3<f64>,
|
||||
c: Vec3<f64>,
|
||||
d: Vec3<f64>,
|
||||
e: Vec3<f64>,
|
||||
) -> bool {
|
||||
let points = vec![a, b, c, d, e];
|
||||
let target = BoundingBox::from_points(&points);
|
||||
let is_in_bounds = points.iter().any(|elem| elem.x() >= p.x())
|
||||
&& points.iter().any(|elem| elem.x() <= p.x())
|
||||
&& points.iter().any(|elem| elem.y() >= p.y())
|
||||
&& points.iter().any(|elem| elem.y() <= p.y())
|
||||
&& points.iter().any(|elem| elem.z() >= p.z())
|
||||
&& points.iter().any(|elem| elem.z() <= p.z());
|
||||
target.contains_point(p) == is_in_bounds
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn no_dimension_is_larger_than_largest_dimension(
|
||||
a: f64,
|
||||
b: f64,
|
||||
c: f64,
|
||||
d: f64,
|
||||
e: f64,
|
||||
f: f64,
|
||||
) -> bool {
|
||||
let target = BoundingBox {
|
||||
bounds: [
|
||||
if a > b {
|
||||
Interval::empty()
|
||||
} else {
|
||||
Interval::new(a, b)
|
||||
},
|
||||
if c > d {
|
||||
Interval::empty()
|
||||
} else {
|
||||
Interval::new(c, d)
|
||||
},
|
||||
if e > f {
|
||||
Interval::empty()
|
||||
} else {
|
||||
Interval::new(e, f)
|
||||
},
|
||||
],
|
||||
};
|
||||
let largest_dimension = target.largest_dimension();
|
||||
let largest_bounds = target.bounds[largest_dimension];
|
||||
if largest_bounds.is_empty() {
|
||||
target.bounds.iter().all(|elem| elem.is_empty())
|
||||
} else {
|
||||
let largest_size = largest_bounds.get_max() - largest_bounds.get_min();
|
||||
target
|
||||
.bounds
|
||||
.iter()
|
||||
.all(|elem| elem.is_empty() || !(largest_size < elem.get_max() - elem.get_min()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
pub enum BinaryTree<Value, LeafValue> {
|
||||
Branch {
|
||||
value: Value,
|
||||
left: Box<Self>,
|
||||
right: Box<Self>,
|
||||
},
|
||||
Leaf {
|
||||
value: LeafValue,
|
||||
},
|
||||
None,
|
||||
}
|
||||
|
||||
impl<Value, LeafValue> BinaryTree<Value, LeafValue> {
|
||||
pub fn count_leaves(&self) -> usize {
|
||||
match self {
|
||||
Self::Branch {
|
||||
value: _,
|
||||
left,
|
||||
right,
|
||||
} => right.count_leaves() + left.count_leaves(),
|
||||
Self::Leaf { value: _ } => 1,
|
||||
Self::None => 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,328 +0,0 @@
|
|||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Interval {
|
||||
min: f64,
|
||||
max: f64,
|
||||
}
|
||||
|
||||
impl Interval {
|
||||
pub fn new(a: f64, b: f64) -> Self {
|
||||
if a > b {
|
||||
Interval { min: b, max: a }
|
||||
} else {
|
||||
Interval { min: a, max: b }
|
||||
}
|
||||
}
|
||||
|
||||
pub fn empty() -> Self {
|
||||
Interval {
|
||||
min: f64::INFINITY,
|
||||
max: f64::NEG_INFINITY,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn infinite() -> Self {
|
||||
Interval {
|
||||
min: f64::NEG_INFINITY,
|
||||
max: f64::INFINITY,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn degenerate(value: f64) -> Self {
|
||||
Interval {
|
||||
min: value,
|
||||
max: value,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_min(&self) -> f64 {
|
||||
self.min
|
||||
}
|
||||
|
||||
pub fn get_max(&self) -> f64 {
|
||||
self.max
|
||||
}
|
||||
|
||||
pub fn is_degenerate(self) -> bool {
|
||||
self.min == self.max
|
||||
}
|
||||
|
||||
pub fn is_empty(self) -> bool {
|
||||
self.min > self.max
|
||||
}
|
||||
|
||||
pub fn contains_value(&self, value: f64) -> bool {
|
||||
value >= self.min && value <= self.max
|
||||
}
|
||||
|
||||
pub fn intersection(self, b: Self) -> Self {
|
||||
Interval {
|
||||
min: self.min.max(b.min),
|
||||
max: self.max.min(b.max),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn union(self, b: Self) -> Self {
|
||||
if self.is_empty() {
|
||||
b
|
||||
} else if b.is_empty() {
|
||||
self
|
||||
} else {
|
||||
Interval {
|
||||
min: self.min.min(b.min),
|
||||
max: self.max.max(b.max),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn expand_to_value(self, v: f64) -> Self {
|
||||
if self.is_empty() {
|
||||
Interval::degenerate(v)
|
||||
} else {
|
||||
Interval {
|
||||
min: self.min.min(v),
|
||||
max: self.max.max(v),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use itertools::{Itertools, MinMaxResult};
|
||||
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[test]
|
||||
fn never_constructed_empty() {
|
||||
let target1 = Interval::new(5f64, 10f64);
|
||||
assert!(!target1.is_empty());
|
||||
let target2 = Interval::new(10f64, 5f64);
|
||||
assert!(!target2.is_empty());
|
||||
let target1 = Interval::new(5f64, -10f64);
|
||||
assert!(!target1.is_empty());
|
||||
let target2 = Interval::new(10f64, -5f64);
|
||||
assert!(!target2.is_empty());
|
||||
let target1 = Interval::new(-5f64, 10f64);
|
||||
assert!(!target1.is_empty());
|
||||
let target2 = Interval::new(-10f64, 5f64);
|
||||
assert!(!target2.is_empty());
|
||||
let target1 = Interval::new(-5f64, -10f64);
|
||||
assert!(!target1.is_empty());
|
||||
let target2 = Interval::new(-10f64, -5f64);
|
||||
assert!(!target2.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_is_empty() {
|
||||
let target: Interval = Interval::empty();
|
||||
assert!(target.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_when_min_greater_than_max() {
|
||||
let target = Interval {
|
||||
min: 10f64,
|
||||
max: 5f64,
|
||||
};
|
||||
assert!(target.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_when_min_greater_than_max_with_negative_values() {
|
||||
let target = Interval {
|
||||
min: -5f64,
|
||||
max: -10f64,
|
||||
};
|
||||
assert!(target.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_when_min_greater_than_max_with_mixed_signs() {
|
||||
let target = Interval {
|
||||
min: 5f64,
|
||||
max: -10f64,
|
||||
};
|
||||
assert!(target.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_is_not_degenerate() {
|
||||
let target = Interval {
|
||||
min: 10f64,
|
||||
max: 5f64,
|
||||
};
|
||||
assert!(!target.is_degenerate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_is_not_degenerate_with_negative_values() {
|
||||
let target = Interval {
|
||||
min: -5f64,
|
||||
max: -10f64,
|
||||
};
|
||||
assert!(!target.is_degenerate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_is_not_degenerate_with_mixed_signs() {
|
||||
let target = Interval {
|
||||
min: -5f64,
|
||||
max: 10f64,
|
||||
};
|
||||
assert!(!target.is_degenerate());
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn no_value_is_in_interval_returned_by_emtpy(value: f64) -> bool {
|
||||
!Interval::empty().contains_value(value)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn identical_min_max_yields_degenerate() {
|
||||
let target = Interval {
|
||||
min: 5f64,
|
||||
max: 5f64,
|
||||
};
|
||||
assert!(target.is_degenerate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_is_degenerate() {
|
||||
let target = Interval::degenerate(5f64);
|
||||
assert!(target.is_degenerate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_is_not_empty() {
|
||||
let target = Interval {
|
||||
min: 5f64,
|
||||
max: 5f64,
|
||||
};
|
||||
assert!(target.is_degenerate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_is_degenerate_with_negative_value() {
|
||||
let target = Interval {
|
||||
min: -5f64,
|
||||
max: -5f64,
|
||||
};
|
||||
assert!(target.is_degenerate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_is_not_empty_with_negative_value() {
|
||||
let target = Interval {
|
||||
min: -5f64,
|
||||
max: -5f64,
|
||||
};
|
||||
assert!(target.is_degenerate());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn degenerate_contains_expected_value() {
|
||||
let target = Interval::degenerate(5f64);
|
||||
assert!(target.contains_value(5.0));
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn degenerate_does_not_contain_any_values_othter_than_expected_value(value: f64) -> bool {
|
||||
let target_value = if value == 5f64 { 5.5 } else { 5f64 };
|
||||
let target = Interval::degenerate(target_value);
|
||||
!target.contains_value(value)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn intersection_with_infinite_is_self() {
|
||||
let target = Interval::new(5.0, 10.0);
|
||||
let result = target.intersection(Interval::infinite());
|
||||
assert!(target.min == result.min);
|
||||
assert!(target.max == result.max);
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn union_with_self_yields_self(a: f64, b: f64) -> bool {
|
||||
let target = Interval::new(a, b);
|
||||
let result = target.union(target);
|
||||
result.min == target.min && result.max == target.max
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn union_yields_min_and_max(a: f64, b: f64, c: f64, d: f64) -> bool {
|
||||
let values = vec![a, b, c, d];
|
||||
if let MinMaxResult::MinMax(&min, &max) =
|
||||
values.iter().minmax_by(|a, b| a.partial_cmp(b).unwrap())
|
||||
{
|
||||
let target1 = Interval::new(a, b);
|
||||
let target2 = Interval::new(c, d);
|
||||
let result = target1.union(target2);
|
||||
result.min == min && result.max == max
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_with_empty_interval_is_correct() {
|
||||
let empty = Interval {
|
||||
min: 1f64,
|
||||
max: -1f64,
|
||||
};
|
||||
let not_empty = Interval {
|
||||
min: 5f64,
|
||||
max: 10f64,
|
||||
};
|
||||
let union1 = not_empty.union(empty);
|
||||
assert!(union1.min == 5.0);
|
||||
assert!(union1.max == 10.0);
|
||||
let union2 = empty.union(not_empty);
|
||||
assert!(union2.min == 5.0);
|
||||
assert!(union2.max == 10.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn union_with_empty_interval_is_correct_when_empty_interval_produced_by_intersection() {
|
||||
let empty = Interval {
|
||||
min: 1f64,
|
||||
max: -1f64,
|
||||
};
|
||||
let not_empty = Interval {
|
||||
min: 5f64,
|
||||
max: 10f64,
|
||||
};
|
||||
let union1 = not_empty.union(empty);
|
||||
assert!(union1.min == 5.0);
|
||||
assert!(union1.max == 10.0);
|
||||
let union2 = empty.union(not_empty);
|
||||
assert!(union2.min == 5.0);
|
||||
assert!(union2.max == 10.0);
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
pub fn expand_to_value_creates_interval_that_includes_value(
|
||||
min: f64,
|
||||
max: f64,
|
||||
value: f64,
|
||||
) -> bool {
|
||||
// Don't check if min <= max, we want to test empty intervals too
|
||||
let interval1 = Interval { min, max };
|
||||
let interval2 = interval1.expand_to_value(value);
|
||||
interval2.contains_value(value)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
pub fn expand_to_value_creates_interval_that_includes_original_interval(
|
||||
b: f64,
|
||||
a: f64,
|
||||
value: f64,
|
||||
) -> bool {
|
||||
let interval1 = Interval::new(a, b);
|
||||
let interval2 = interval1.expand_to_value(value);
|
||||
let interval3 = interval2.intersection(interval1);
|
||||
// If interval2 contains interval1, that the intersection of the two will
|
||||
// be equal to interval1
|
||||
interval1.min == interval3.min && interval1.max == interval3.max
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +0,0 @@
|
|||
mod interval;
|
||||
pub use interval::Interval;
|
||||
|
||||
pub mod algebra_utils;
|
||||
pub mod array2d;
|
||||
pub use array2d::Array2D;
|
||||
pub mod axis_aligned_bounding_box;
|
||||
pub mod binary_tree;
|
||||
pub mod morton;
|
||||
pub mod normalizer;
|
||||
mod tile_iterator;
|
||||
pub use tile_iterator::{Tile, TileIterator};
|
||||
pub mod polyhedra;
|
||||
|
|
@ -1,45 +0,0 @@
|
|||
use crate::math::Vec3;
|
||||
use crate::realtype::NormalizedToU32;
|
||||
|
||||
fn spread_bits(v: u32) -> u32 {
|
||||
let mut result = 0;
|
||||
for power in 0..9 {
|
||||
result |= ((1 << power) & v) << (power * 2);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn morton_order_value_3d(p: Vec3<f64>) -> u32 {
|
||||
let x = p.x().normalized_to_u32(10);
|
||||
let y = p.y().normalized_to_u32(10);
|
||||
let z = p.z().normalized_to_u32(10);
|
||||
(spread_bits(x) << 2) | (spread_bits(y) << 1) | spread_bits(z)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
mod spread_bits {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn zero_yields_zero() {
|
||||
assert!(spread_bits(0) == 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn one_yields_one() {
|
||||
assert!(spread_bits(1) == 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b1111_yields_b1001001001() {
|
||||
assert!(spread_bits(0b1111) == 0b1001001001);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn b1010_yields_b1000001000() {
|
||||
assert!(spread_bits(0b1010) == 0b1000001000);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,178 +0,0 @@
|
|||
use super::axis_aligned_bounding_box::BoundingBox;
|
||||
use super::Interval;
|
||||
|
||||
use crate::math::Vec3;
|
||||
|
||||
use itertools::izip;
|
||||
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
pub struct RealNormalizer {
|
||||
min: f64,
|
||||
range: f64,
|
||||
}
|
||||
|
||||
impl RealNormalizer {
|
||||
pub fn new(interval: Interval) -> Self {
|
||||
let min = interval.get_min();
|
||||
let range = interval.get_max() - min;
|
||||
Self { min, range }
|
||||
}
|
||||
|
||||
pub fn normalize(&self, value: f64) -> f64 {
|
||||
(value - self.min) / self.range
|
||||
}
|
||||
|
||||
pub fn normalize_and_clamp(&self, value: f64) -> f64 {
|
||||
((value - self.min) / self.range).clamp(0.0, 1.0)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct Point3Normalizer {
|
||||
dimension_normalizers: [RealNormalizer; 3],
|
||||
}
|
||||
|
||||
impl Point3Normalizer {
|
||||
pub fn new(bounds: BoundingBox) -> Self {
|
||||
let mut normalizer = Point3Normalizer {
|
||||
dimension_normalizers: [RealNormalizer::new(Interval::empty()); 3],
|
||||
};
|
||||
for (normalizer, &bounds) in normalizer
|
||||
.dimension_normalizers
|
||||
.iter_mut()
|
||||
.zip(bounds.bounds.iter())
|
||||
{
|
||||
*normalizer = RealNormalizer::new(bounds);
|
||||
}
|
||||
normalizer
|
||||
}
|
||||
|
||||
pub fn normalize(&self, point: Vec3<f64>) -> Vec3<f64> {
|
||||
let mut result = Vec3::new(0.0, 0.0, 0.0);
|
||||
for (value_out, &value_in, normalizer) in izip!(
|
||||
result.coords.iter_mut(),
|
||||
point.coords.iter(),
|
||||
self.dimension_normalizers.iter()
|
||||
) {
|
||||
*value_out = normalizer.normalize(value_in);
|
||||
}
|
||||
result
|
||||
}
|
||||
|
||||
pub fn normalize_and_clamp(&self, point: Vec3<f64>) -> Vec3<f64> {
|
||||
let mut result = Vec3::new(0.0, 0.0, 0.0);
|
||||
for (value_out, &value_in, normalizer) in izip!(
|
||||
result.coords.iter_mut(),
|
||||
point.coords.iter(),
|
||||
self.dimension_normalizers.iter()
|
||||
) {
|
||||
*value_out = normalizer.normalize_and_clamp(value_in);
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test {
|
||||
use super::*;
|
||||
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_zero_to_one_yields_input(value: f64) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(0.0, 1.0));
|
||||
target.normalize(value) == value
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_two_to_three_yields_input_minus_two(value: f64) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(2.0, 3.0));
|
||||
target.normalize(value) == value - 2.0
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_negative_three_to_negative_two_yields_input_plus_three(value: f64) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(-3.0, -2.0));
|
||||
target.normalize(value) == value + 3.0
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_zero_to_two_yields_input_divided_by_two(value: f64) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(0.0, 2.0));
|
||||
target.normalize(value) == value / 2.0
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_two_to_four_yields_zero_when_input_is_two() {
|
||||
let target = RealNormalizer::new(Interval::new(2.0, 4.0));
|
||||
assert!(target.normalize(2.0) == 0.0)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn normalize_two_to_four_yields_one_when_input_is_four() {
|
||||
let target = RealNormalizer::new(Interval::new(2.0, 4.0));
|
||||
assert!(target.normalize(4.0) == 1.0)
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_two_to_four_yields_input_divided_by_two_minus_one(value: f64) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(2.0, 4.0));
|
||||
target.normalize(value) == (value - 2.0) / 2.0
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_and_clamp_two_to_four_yields_zero_when_input_less_than_or_equal_two(
|
||||
value: f64,
|
||||
) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(2.0, 4.0));
|
||||
target.normalize_and_clamp(value) == 0.0 || value > 2.0
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_and_clamp_two_to_four_yields_one_when_input_greater_than_or_equal_four(
|
||||
value: f64,
|
||||
) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(2.0, 4.0));
|
||||
target.normalize_and_clamp(value) == 1.0 || value < 4.0
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_and_clamp_two_to_four_yields_same_value_as_normalize_when_in_range(
|
||||
value: f64,
|
||||
) -> bool {
|
||||
let target = RealNormalizer::new(Interval::new(2.0, 4.0));
|
||||
target.normalize_and_clamp(value) == target.normalize(value) || value < 2.0 || value > 4.0
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_point3_is_the_same_as_normalize_each_dimension(
|
||||
a: Vec3<f64>,
|
||||
b: Vec3<f64>,
|
||||
c: Vec3<f64>,
|
||||
) -> bool {
|
||||
let x_normalizer = RealNormalizer::new(Interval::new(a.x().min(b.x()), a.x().max(b.x())));
|
||||
let y_normalizer = RealNormalizer::new(Interval::new(a.y().min(b.y()), a.y().max(b.y())));
|
||||
let z_normalizer = RealNormalizer::new(Interval::new(a.z().min(b.z()), a.z().max(b.z())));
|
||||
let xyz_normalizer = Point3Normalizer::new(BoundingBox::from_corners(a, b));
|
||||
let normalized_point = xyz_normalizer.normalize(c);
|
||||
x_normalizer.normalize(c.x()) == normalized_point.x()
|
||||
&& y_normalizer.normalize(c.y()) == normalized_point.y()
|
||||
&& z_normalizer.normalize(c.z()) == normalized_point.z()
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn normalize_and_clamp_point3_is_the_same_as_normalize_and_clamp_each_dimension(
|
||||
a: Vec3<f64>,
|
||||
b: Vec3<f64>,
|
||||
c: Vec3<f64>,
|
||||
) -> bool {
|
||||
let x_normalizer = RealNormalizer::new(Interval::new(a.x().min(b.x()), a.x().max(b.x())));
|
||||
let y_normalizer = RealNormalizer::new(Interval::new(a.y().min(b.y()), a.y().max(b.y())));
|
||||
let z_normalizer = RealNormalizer::new(Interval::new(a.z().min(b.z()), a.z().max(b.z())));
|
||||
let xyz_normalizer = Point3Normalizer::new(BoundingBox::from_corners(a, b));
|
||||
let normalized_point = xyz_normalizer.normalize_and_clamp(c);
|
||||
x_normalizer.normalize_and_clamp(c.x()) == normalized_point.x()
|
||||
&& y_normalizer.normalize_and_clamp(c.y()) == normalized_point.y()
|
||||
&& z_normalizer.normalize_and_clamp(c.z()) == normalized_point.z()
|
||||
}
|
||||
}
|
||||
|
|
@ -1,131 +0,0 @@
|
|||
use itertools::izip;
|
||||
|
||||
use crate::materials::Material;
|
||||
use crate::math::Vec3;
|
||||
use crate::raycasting::{Primitive, Triangle};
|
||||
|
||||
use std::sync::Arc;
|
||||
|
||||
pub fn triangulate_polygon(
|
||||
vertices: &[Vec3<f64>],
|
||||
normal: &Vec3<f64>,
|
||||
material: Arc<dyn Material>,
|
||||
) -> Vec<Arc<dyn Primitive>> {
|
||||
assert!(vertices.len() >= 3);
|
||||
let hinge = vertices[0];
|
||||
izip!(vertices.iter().skip(1), vertices.iter().skip(2))
|
||||
.map(|(a, b)| {
|
||||
Arc::new(Triangle {
|
||||
vertices: [hinge, *a, *b],
|
||||
normals: [*normal, *normal, *normal],
|
||||
material: Arc::clone(&material),
|
||||
}) as Arc<dyn Primitive>
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn generate_dodecahedron(
|
||||
centre: Vec3<f64>,
|
||||
size: f64,
|
||||
material: Arc<dyn Material>,
|
||||
) -> Vec<Arc<dyn Primitive>> {
|
||||
let phi = (1.0 + (5.0_f64).sqrt()) / 2.0;
|
||||
let phi_inv = 1.0 / phi;
|
||||
|
||||
let faces = vec![
|
||||
vec![
|
||||
Vec3::new(phi_inv, 0.0, phi),
|
||||
Vec3::new(-phi_inv, 0.0, phi),
|
||||
Vec3::new(-1.0, -1.0, 1.0),
|
||||
Vec3::new(0.0, -phi, phi_inv),
|
||||
Vec3::new(1.0, -1.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(phi_inv, 0.0, phi),
|
||||
Vec3::new(-phi_inv, 0.0, phi),
|
||||
Vec3::new(-1.0, 1.0, 1.0),
|
||||
Vec3::new(0.0, phi, phi_inv),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(phi_inv, 0.0, phi),
|
||||
Vec3::new(1.0, -1.0, 1.0),
|
||||
Vec3::new(phi, -phi_inv, 0.0),
|
||||
Vec3::new(phi, phi_inv, 0.0),
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(-phi_inv, 0.0, phi),
|
||||
Vec3::new(-1.0, -1.0, 1.0),
|
||||
Vec3::new(-phi, -phi_inv, 0.0),
|
||||
Vec3::new(-phi, phi_inv, 0.0),
|
||||
Vec3::new(-1.0, 1.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(-1.0, -1.0, 1.0),
|
||||
Vec3::new(-phi, -phi_inv, 0.0),
|
||||
Vec3::new(-1.0, -1.0, -1.0),
|
||||
Vec3::new(0.0, -phi, -phi_inv),
|
||||
Vec3::new(0.0, -phi, phi_inv),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(0.0, -phi, phi_inv),
|
||||
Vec3::new(0.0, -phi, -phi_inv),
|
||||
Vec3::new(1.0, -1.0, -1.0),
|
||||
Vec3::new(phi, -phi_inv, 0.0),
|
||||
Vec3::new(1.0, -1.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(0.0, phi, phi_inv),
|
||||
Vec3::new(0.0, phi, -phi_inv),
|
||||
Vec3::new(-1.0, 1.0, -1.0),
|
||||
Vec3::new(-phi, phi_inv, 0.0),
|
||||
Vec3::new(-1.0, 1.0, 1.0),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(1.0, 1.0, 1.0),
|
||||
Vec3::new(phi, phi_inv, 0.0),
|
||||
Vec3::new(1.0, 1.0, -1.0),
|
||||
Vec3::new(0.0, phi, -phi_inv),
|
||||
Vec3::new(0.0, phi, phi_inv),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(1.0, -1.0, -1.0),
|
||||
Vec3::new(0.0, -phi, -phi_inv),
|
||||
Vec3::new(-1.0, -1.0, -1.0),
|
||||
Vec3::new(-phi_inv, 0.0, -phi),
|
||||
Vec3::new(phi_inv, 0.0, -phi),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(1.0, 1.0, -1.0),
|
||||
Vec3::new(0.0, phi, -phi_inv),
|
||||
Vec3::new(-1.0, 1.0, -1.0),
|
||||
Vec3::new(-phi_inv, 0.0, -phi),
|
||||
Vec3::new(phi_inv, 0.0, -phi),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(1.0, 1.0, -1.0),
|
||||
Vec3::new(phi, phi_inv, 0.0),
|
||||
Vec3::new(phi, -phi_inv, 0.0),
|
||||
Vec3::new(1.0, -1.0, -1.0),
|
||||
Vec3::new(phi_inv, 0.0, -phi),
|
||||
],
|
||||
vec![
|
||||
Vec3::new(-1.0, 1.0, -1.0),
|
||||
Vec3::new(-phi, phi_inv, 0.0),
|
||||
Vec3::new(-phi, -phi_inv, 0.0),
|
||||
Vec3::new(-1.0, -1.0, -1.0),
|
||||
Vec3::new(-phi_inv, 0.0, -phi),
|
||||
],
|
||||
];
|
||||
|
||||
let scale = size * 3f64.sqrt() / 2.0;
|
||||
faces
|
||||
.iter()
|
||||
.flat_map(|face| {
|
||||
let normal = (face[1] - face[0]).cross(&(face[2] - face[1]));
|
||||
let transformed_face: Vec<_> = face.iter().map(|v| centre + v * scale).collect();
|
||||
triangulate_polygon(&transformed_face, &normal, Arc::clone(&material))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
|
@ -1,155 +0,0 @@
|
|||
#[derive(Copy, Clone, Debug)]
|
||||
pub struct Tile {
|
||||
pub start_column: usize,
|
||||
pub end_column: usize,
|
||||
pub start_row: usize,
|
||||
pub end_row: usize,
|
||||
}
|
||||
|
||||
impl Tile {
|
||||
pub fn width(&self) -> usize {
|
||||
self.end_column - self.start_column
|
||||
}
|
||||
pub fn height(&self) -> usize {
|
||||
self.end_row - self.start_row
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct TileIterator {
|
||||
tile_size: usize,
|
||||
total_height: usize,
|
||||
total_width: usize,
|
||||
current_column: usize,
|
||||
current_row: usize,
|
||||
}
|
||||
|
||||
impl TileIterator {
|
||||
pub fn new(total_width: usize, total_height: usize, tile_size: usize) -> TileIterator {
|
||||
// If tile_size*2 is greater than usize::max_value(), increment would overflow
|
||||
assert!(tile_size > 0 && tile_size * 2 < usize::MAX);
|
||||
TileIterator {
|
||||
tile_size,
|
||||
total_width,
|
||||
total_height,
|
||||
current_column: 0,
|
||||
current_row: 0,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for TileIterator {
|
||||
type Item = Tile;
|
||||
|
||||
fn next(&mut self) -> Option<Tile> {
|
||||
if self.current_row >= self.total_height {
|
||||
None
|
||||
} else {
|
||||
let start_column = self.current_column;
|
||||
let end_column = self.total_width.min(start_column + self.tile_size);
|
||||
let start_row = self.current_row;
|
||||
let end_row = self.total_height.min(start_row + self.tile_size);
|
||||
|
||||
self.current_column += self.tile_size;
|
||||
if self.current_column >= self.total_width {
|
||||
self.current_row += self.tile_size;
|
||||
self.current_column = 0;
|
||||
}
|
||||
|
||||
Some(Tile {
|
||||
start_column,
|
||||
end_column,
|
||||
start_row,
|
||||
end_row,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
use quickcheck::TestResult;
|
||||
use quickcheck_macros::quickcheck;
|
||||
|
||||
#[test]
|
||||
fn iterator_has_correct_number_of_tiles_when_dimensions_are_multiple_of_tile_size() {
|
||||
let target = TileIterator::new(20, 15, 5);
|
||||
assert!(target.count() == 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_has_correct_number_of_tiles_when_width_is_one_under_multiple_of_tile_size() {
|
||||
let target = TileIterator::new(19, 15, 5);
|
||||
assert!(target.count() == 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_has_correct_number_of_tiles_when_width_is_one_over_multiple_of_tile_size() {
|
||||
let target = TileIterator::new(21, 15, 5);
|
||||
assert!(target.count() == 15);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_has_correct_number_of_tiles_when_height_is_one_under_multiple_of_tile_size() {
|
||||
let target = TileIterator::new(20, 14, 5);
|
||||
assert!(target.count() == 12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn iterator_has_correct_number_of_tiles_when_height_is_one_over_multiple_of_tile_size() {
|
||||
let target = TileIterator::new(20, 16, 5);
|
||||
assert!(target.count() == 16);
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn tiles_are_expected_size(width: usize, height: usize, tile_size: usize) -> TestResult {
|
||||
let max_size = 10000;
|
||||
// Check size of width and height first, since width*height might overflow.
|
||||
if width > max_size || height > max_size || width * height > max_size {
|
||||
return TestResult::discard();
|
||||
}
|
||||
if tile_size == 0 {
|
||||
return TestResult::discard();
|
||||
}
|
||||
|
||||
let mut target = TileIterator::new(width, height, tile_size);
|
||||
TestResult::from_bool(target.all(|tile| {
|
||||
tile.end_column - tile.start_column <= tile_size
|
||||
&& tile.end_row - tile.start_row <= tile_size
|
||||
}))
|
||||
}
|
||||
|
||||
#[quickcheck]
|
||||
fn iterator_includes_all_coordinates_exactly_once(
|
||||
width: usize,
|
||||
height: usize,
|
||||
tile_size: usize,
|
||||
) -> TestResult {
|
||||
let max_size = 10000;
|
||||
// Check size of width and height first, since width*height might overflow.
|
||||
if width > max_size || height > max_size || width * height > max_size {
|
||||
return TestResult::discard();
|
||||
}
|
||||
if tile_size == 0 {
|
||||
return TestResult::discard();
|
||||
}
|
||||
|
||||
let target = TileIterator::new(width, height, tile_size);
|
||||
let mut index_counts = vec![0; width * height];
|
||||
let mut total_count = 0;
|
||||
for tile in target {
|
||||
for column in tile.start_column..tile.end_column {
|
||||
for row in tile.start_row..tile.end_row {
|
||||
index_counts[row * width + column] += 1;
|
||||
total_count += 1;
|
||||
if total_count > width * height {
|
||||
return TestResult::failed();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
TestResult::from_bool(index_counts.iter().all(|&elem| elem == 1))
|
||||
}
|
||||
}
|
||||
|
|
@ -1,2 +0,0 @@
|
|||
Thanks to the Stanford Computer Graphics Laboratory (https://graphics.stanford.edu/data/3Dscanrep/)
|
||||
for the famous Stanford Bunny model.
|
||||
BIN
test_data/stanford_bunny.obj (Stored with Git LFS)
BIN
test_data/stanford_bunny.obj (Stored with Git LFS)
Binary file not shown.
Loading…
Reference in New Issue