Added basic structure of the project from ria-weekend project
1. Added Motion Blur Demo
This commit is contained in:
commit
35259ad16d
3
.gitignore
vendored
Normal file
3
.gitignore
vendored
Normal file
|
@ -0,0 +1,3 @@
|
|||
target/
|
||||
*.ppm
|
||||
*.png
|
267
Cargo.lock
generated
Normal file
267
Cargo.lock
generated
Normal file
|
@ -0,0 +1,267 @@
|
|||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
[[package]]
|
||||
name = "autocfg"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d"
|
||||
|
||||
[[package]]
|
||||
name = "bitflags"
|
||||
version = "1.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cf1de2fe8c75bc145a2f577add951f8134889b4795d47466a54a5c846d691693"
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "0.1.10"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822"
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-deque"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9f02af974daeee82218205558e51ec8768b48cf524bd01d550abe5573a608285"
|
||||
dependencies = [
|
||||
"crossbeam-epoch",
|
||||
"crossbeam-utils",
|
||||
"maybe-uninit",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-epoch"
|
||||
version = "0.8.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"maybe-uninit",
|
||||
"memoffset",
|
||||
"scopeguard",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-queue"
|
||||
version = "0.2.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c695eeca1e7173472a32221542ae469b3e9aac3a4fc81f7696bcad82029493db"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"crossbeam-utils",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "crossbeam-utils"
|
||||
version = "0.7.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8"
|
||||
dependencies = [
|
||||
"autocfg",
|
||||
"cfg-if",
|
||||
"lazy_static",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "either"
|
||||
version = "1.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "bb1f6b1ce1c140482ea30ddd3335fc0024ac7ee112895426e0a629a6c20adfe3"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7abc8dd8451921606d809ba32e95b6111925cd2906060d2dcc29c070220503eb"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hermit-abi"
|
||||
version = "0.1.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1010591b26bbfe835e9faeabeb11866061cc7dcebffd56ad7d0942d0e61aefd8"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "lazy_static"
|
||||
version = "1.4.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646"
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.67"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb147597cdf94ed43ab7a9038716637d2d1bf2bc571da995d0028dec06bd3018"
|
||||
|
||||
[[package]]
|
||||
name = "maybe-uninit"
|
||||
version = "2.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "60302e4db3a61da70c0cb7991976248362f30319e88850c487b9b95bbf059e00"
|
||||
|
||||
[[package]]
|
||||
name = "memoffset"
|
||||
version = "0.5.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "75189eb85871ea5c2e2c15abbdd541185f63b408415e5051f5cac122d8c774b9"
|
||||
dependencies = [
|
||||
"rustc_version",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num_cpus"
|
||||
version = "1.12.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "46203554f085ff89c235cd12f7075f3233af9b11ed7c9e16dfe2560d03313ce6"
|
||||
dependencies = [
|
||||
"hermit-abi",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.6"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "74490b50b9fbe561ac330df47c08f3f33073d2d00c150f719147d7c54522fa1b"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.7.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a6b1679d49b24bbfe0c803429aa1874472f50d9b363131f0e89fc356b544d03"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
"rand_hc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.2.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4c8ed856279c9737206bf725bf36935d8666ead7aa69b52be55af369d193402"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "90bde5296fc891b0cef12a6d03ddccc162ce7b2aff54160af9338f8d40df6d19"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_hc"
|
||||
version = "0.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ca3129af7b92a17112d59ad498c6f81eaf463253766b90396d39ea7a39d6613c"
|
||||
dependencies = [
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon"
|
||||
version = "1.3.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db6ce3297f9c85e16621bb8cca38a06779ffc31bb8184e1be4bed2be4678a098"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"either",
|
||||
"rayon-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rayon-core"
|
||||
version = "1.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "08a89b46efaf957e52b18062fb2f4660f8b8a4dde1807ca002690868ef2c85a9"
|
||||
dependencies = [
|
||||
"crossbeam-deque",
|
||||
"crossbeam-queue",
|
||||
"crossbeam-utils",
|
||||
"lazy_static",
|
||||
"num_cpus",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rtnw"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
"rayon",
|
||||
"sdl2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustc_version"
|
||||
version = "0.2.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "138e3e0acb6c9fb258b19b67cb8abd63c00679d2851805ea151465464fe9030a"
|
||||
dependencies = [
|
||||
"semver",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "scopeguard"
|
||||
version = "1.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd"
|
||||
|
||||
[[package]]
|
||||
name = "sdl2"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1f74124048ea86b5cd50236b2443f6f57cf4625a8e8818009b4e50dbb8729a43"
|
||||
dependencies = [
|
||||
"bitflags",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"sdl2-sys",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "sdl2-sys"
|
||||
version = "0.33.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "c2e1deb61ff274d29fb985017d4611d4004b113676eaa9c06754194caf82094e"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver"
|
||||
version = "0.9.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1d7eb9ef2c18661902cc47e535f9bc51b78acd254da71d375c2f6720d9a40403"
|
||||
dependencies = [
|
||||
"semver-parser",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "semver-parser"
|
||||
version = "0.7.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3"
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.9.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "cccddf32554fecc6acb585f82a32a72e28b48f8c4c1883ddfeeeaa96f7d8e519"
|
10
Cargo.toml
Normal file
10
Cargo.toml
Normal file
|
@ -0,0 +1,10 @@
|
|||
[package]
|
||||
name = "rtnw"
|
||||
version = "0.1.0"
|
||||
authors = ["ishanjain28 <ishanjain28@gmail.com>"]
|
||||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
sdl2 = "0.33.0"
|
||||
rand = "0.7.3"
|
||||
rayon = "1.3.0"
|
8
README.md
Normal file
8
README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# Ray Tracing the Next Week
|
||||
|
||||
This is my attempt at Ray Tracing the Next Week book by Peter Shirley.
|
||||
|
||||
# Demo Renders
|
||||
|
||||
![[1] Motion Blur](https://dl.ishanjain.me/images/motion_blur-2000x1000.png)
|
||||
[1] Motion Blur
|
91
src/camera.rs
Normal file
91
src/camera.rs
Normal file
|
@ -0,0 +1,91 @@
|
|||
use {
|
||||
crate::types::{Ray, Vec3},
|
||||
rand::Rng,
|
||||
};
|
||||
|
||||
pub struct Camera {
|
||||
origin: Vec3,
|
||||
horizontal: Vec3,
|
||||
vertical: Vec3,
|
||||
lower_left_corner: Vec3,
|
||||
lens_radius: f32,
|
||||
|
||||
// position vectors
|
||||
u: Vec3,
|
||||
v: Vec3,
|
||||
w: Vec3,
|
||||
|
||||
shutter_open: f32,
|
||||
shutter_close: f32,
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
// vertical_fov is the viewable angle from top->bottom
|
||||
// look_from is basically camera position
|
||||
// look_at is the point where camera is looking
|
||||
// v_up is camera's up vector. i.e. it points upwards from the camera
|
||||
// orthogonal to look_from - look_at vector
|
||||
pub fn new(
|
||||
look_from: Vec3,
|
||||
look_at: Vec3,
|
||||
v_up: Vec3,
|
||||
vertical_fov: f32,
|
||||
aspect: f32,
|
||||
aperture: f32,
|
||||
focus_distance: f32,
|
||||
shutter_open: f32,
|
||||
shutter_close: f32,
|
||||
) -> Self {
|
||||
// convert degree to radian
|
||||
let angle = vertical_fov * std::f32::consts::PI / 180.0;
|
||||
let half_height = (angle / 2.0).tan();
|
||||
let half_width = aspect * half_height;
|
||||
|
||||
let origin = look_from;
|
||||
let w = (look_from - look_at).unit_vector();
|
||||
let u = v_up.cross(&w).unit_vector();
|
||||
let v = w.cross(&u);
|
||||
|
||||
let lower_left_corner = origin
|
||||
- u * focus_distance * half_width
|
||||
- v * focus_distance * half_height
|
||||
- w * focus_distance;
|
||||
let horizontal = u * half_width * focus_distance * 2.0;
|
||||
let vertical = v * half_height * focus_distance * 2.0;
|
||||
let lens_radius = aperture / 2.0;
|
||||
|
||||
Self {
|
||||
lens_radius,
|
||||
lower_left_corner,
|
||||
horizontal,
|
||||
vertical,
|
||||
origin,
|
||||
u,
|
||||
v,
|
||||
w,
|
||||
shutter_open,
|
||||
shutter_close,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_ray(&self, u: f32, v: f32) -> Ray {
|
||||
let mut rng = rand::thread_rng();
|
||||
let rd = random_in_unit_disk(&mut rng) * self.lens_radius;
|
||||
let offset = self.u * rd.x() + self.v * rd.y();
|
||||
let time = self.shutter_open + rng.gen::<f32>() * (self.shutter_close - self.shutter_open);
|
||||
Ray::new(
|
||||
self.origin + offset,
|
||||
self.lower_left_corner + self.horizontal * u + self.vertical * v - self.origin - offset,
|
||||
time,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn random_in_unit_disk(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
|
||||
let mut p = Vec3::new(rng.gen::<f32>(), rng.gen::<f32>(), 0.0) * 2.0 - Vec3::new(1.0, 1.0, 0.0);
|
||||
|
||||
while p.dot(&p) >= 1.0 {
|
||||
p = Vec3::new(rng.gen::<f32>(), rng.gen::<f32>(), 0.0) * 2.0 - Vec3::new(1.0, 0.0, 0.0);
|
||||
}
|
||||
p
|
||||
}
|
117
src/demos/mod.rs
Normal file
117
src/demos/mod.rs
Normal file
|
@ -0,0 +1,117 @@
|
|||
use {
|
||||
crate::{
|
||||
types::{HitableList, Vec3},
|
||||
Camera, HORIZONTAL_PARTITION, VERTICAL_PARTITION,
|
||||
},
|
||||
rayon::prelude::*,
|
||||
std::{
|
||||
fs::File,
|
||||
io::Write,
|
||||
sync::{Arc, Mutex},
|
||||
},
|
||||
};
|
||||
|
||||
mod motion_blur;
|
||||
|
||||
pub use motion_blur::MotionBlur;
|
||||
|
||||
pub struct Chunk {
|
||||
x: usize,
|
||||
y: usize,
|
||||
nx: usize,
|
||||
ny: usize,
|
||||
start_x: usize,
|
||||
start_y: usize,
|
||||
buffer: Vec<u8>,
|
||||
}
|
||||
|
||||
pub trait Demo: std::marker::Sync {
|
||||
fn render(&self, buf: &mut Vec<u8>, width: usize, height: usize, samples: u8) {
|
||||
let nx = width / VERTICAL_PARTITION;
|
||||
let ny = height / HORIZONTAL_PARTITION;
|
||||
let world = self.world();
|
||||
let camera = self.camera(nx as f32 / ny as f32);
|
||||
|
||||
let buf = Arc::new(Mutex::new(buf));
|
||||
|
||||
(0..VERTICAL_PARTITION).into_par_iter().for_each(|j| {
|
||||
let buf = buf.clone();
|
||||
(0..HORIZONTAL_PARTITION).into_par_iter().for_each(|i| {
|
||||
let start_y = j * ny;
|
||||
let start_x = i * nx;
|
||||
let x = width;
|
||||
let y = height;
|
||||
let mut chunk = Chunk {
|
||||
x,
|
||||
y,
|
||||
nx,
|
||||
ny,
|
||||
start_x,
|
||||
start_y,
|
||||
buffer: vec![0; nx * ny * 4],
|
||||
};
|
||||
self.render_chunk(&mut chunk, camera.as_ref(), world.as_ref(), samples);
|
||||
|
||||
let mut buf = buf.lock().unwrap();
|
||||
|
||||
let mut temp_offset = 0;
|
||||
for j in start_y..start_y + ny {
|
||||
let real_offset = ((y - j - 1) * x + start_x) * 4;
|
||||
|
||||
buf[real_offset..real_offset + nx * 4]
|
||||
.copy_from_slice(&chunk.buffer[temp_offset..temp_offset + nx * 4]);
|
||||
|
||||
temp_offset += nx * 4;
|
||||
}
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
fn world(&self) -> Option<HitableList> {
|
||||
None
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f32) -> Option<Camera> {
|
||||
let lookfrom = Vec3::new(0.0, 0.0, 0.0);
|
||||
let lookat = Vec3::new(0.0, 0.0, -1.0);
|
||||
Some(Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
90.0,
|
||||
aspect_ratio,
|
||||
0.0, //aperture
|
||||
1.0, // focus_distance
|
||||
0.0, // shutter open time
|
||||
1.0, // shutter close time
|
||||
))
|
||||
}
|
||||
|
||||
fn render_chunk(
|
||||
&self,
|
||||
chunk: &mut Chunk,
|
||||
camera: Option<&Camera>,
|
||||
world: Option<&HitableList>,
|
||||
samples: u8,
|
||||
);
|
||||
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
fn save_as_ppm(&self, buf: &[u8], width: usize, height: usize) {
|
||||
let header = format!("P3\n{} {}\n255\n", width, height);
|
||||
|
||||
let mut file = match File::create(&format!("{}-{}x{}.ppm", self.name(), width, height)) {
|
||||
Ok(file) => file,
|
||||
Err(e) => panic!("couldn't create {}: {}", self.name(), e),
|
||||
};
|
||||
file.write_all(header.as_bytes())
|
||||
.expect("error in writing file header");
|
||||
|
||||
for i in buf.chunks(4) {
|
||||
match file.write_all(format!("{} {} {}\n", i[0], i[1], i[2]).as_bytes()) {
|
||||
Ok(_) => (),
|
||||
Err(e) => panic!("couldn't write to {}: {}", self.name(), e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
185
src/demos/motion_blur.rs
Normal file
185
src/demos/motion_blur.rs
Normal file
|
@ -0,0 +1,185 @@
|
|||
use {
|
||||
crate::{
|
||||
demos::{Chunk, Demo},
|
||||
types::{
|
||||
material::{Dielectric, Lambertian, Metal},
|
||||
Hitable, HitableList, MovingSphere, Ray, Sphere, Vec3,
|
||||
},
|
||||
Camera,
|
||||
},
|
||||
rand::Rng,
|
||||
};
|
||||
|
||||
const RANGE: i32 = 10;
|
||||
|
||||
pub struct MotionBlur;
|
||||
|
||||
impl Demo for MotionBlur {
|
||||
fn name(&self) -> &'static str {
|
||||
"motion_blur"
|
||||
}
|
||||
|
||||
fn world(&self) -> Option<HitableList> {
|
||||
let mut world = HitableList {
|
||||
list: Vec::with_capacity(500),
|
||||
};
|
||||
|
||||
world.push(Box::new(Sphere::new(
|
||||
Vec3::new(0.0, -1000.0, 0.0),
|
||||
1000.0,
|
||||
Box::new(Lambertian::new(Vec3::new(0.5, 0.5, 0.5))),
|
||||
)));
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let radius = 0.2;
|
||||
let l = Vec3::new(4.0, 0.2, 0.0);
|
||||
|
||||
for a in -RANGE..RANGE {
|
||||
let a = a as f32;
|
||||
for b in -RANGE..RANGE {
|
||||
let b = b as f32;
|
||||
let choose_material_probability = rng.gen::<f32>();
|
||||
let center = Vec3::new(a + 0.9 * rng.gen::<f32>(), 0.2, b + 0.9 * rng.gen::<f32>());
|
||||
|
||||
if (center - l).length() > 0.9 {
|
||||
if choose_material_probability < 0.8 {
|
||||
// diffuse material
|
||||
world.push(Box::new(MovingSphere::new(
|
||||
center,
|
||||
center + Vec3::new(0.0, 0.5 * rng.gen::<f32>(), 0.0),
|
||||
0.0,
|
||||
1.0,
|
||||
radius,
|
||||
Box::new(Lambertian::new(Vec3::new(
|
||||
rng.gen::<f32>() * rng.gen::<f32>(),
|
||||
rng.gen::<f32>() * rng.gen::<f32>(),
|
||||
rng.gen::<f32>() * rng.gen::<f32>(),
|
||||
))),
|
||||
)));
|
||||
} else if choose_material_probability < 0.95 {
|
||||
// metal material
|
||||
world.push(Box::new(Sphere::new(
|
||||
center,
|
||||
radius,
|
||||
Box::new(Metal::with_fuzz(
|
||||
Vec3::new(
|
||||
(1.0 + rng.gen::<f32>()) * 0.5,
|
||||
(1.0 + rng.gen::<f32>()) * 0.5,
|
||||
(1.0 + rng.gen::<f32>()) * 0.5,
|
||||
),
|
||||
0.5 * rng.gen::<f32>(),
|
||||
)),
|
||||
)));
|
||||
} else {
|
||||
// glass material
|
||||
world.push(Box::new(Sphere::new(
|
||||
center,
|
||||
radius,
|
||||
Box::new(Dielectric::new(1.5)),
|
||||
)));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
world.push(Box::new(Sphere::new(
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
1.0,
|
||||
Box::new(Dielectric::new(1.5)),
|
||||
)));
|
||||
world.push(Box::new(Sphere::new(
|
||||
Vec3::new(-4.0, 1.0, 0.0),
|
||||
1.0,
|
||||
Box::new(Lambertian::new(Vec3::new(0.4, 0.2, 0.1))),
|
||||
)));
|
||||
world.push(Box::new(Sphere::new(
|
||||
Vec3::new(4.0, 1.0, 0.0),
|
||||
1.0,
|
||||
Box::new(Metal::with_fuzz(Vec3::new(0.7, 0.6, 0.5), 0.0)),
|
||||
)));
|
||||
|
||||
Some(world)
|
||||
}
|
||||
|
||||
fn camera(&self, aspect_ratio: f32) -> Option<Camera> {
|
||||
let lookfrom = Vec3::new(13.0, 2.0, 3.0);
|
||||
let lookat = Vec3::new(0.0, 0.0, 0.0);
|
||||
let aperture = 0.1;
|
||||
let focus_distance = 10.0;
|
||||
let camera = Camera::new(
|
||||
lookfrom,
|
||||
lookat,
|
||||
Vec3::new(0.0, 1.0, 0.0),
|
||||
20.0,
|
||||
aspect_ratio,
|
||||
aperture,
|
||||
focus_distance,
|
||||
0.0,
|
||||
1.0,
|
||||
);
|
||||
Some(camera)
|
||||
}
|
||||
|
||||
fn render_chunk(
|
||||
&self,
|
||||
chunk: &mut Chunk,
|
||||
camera: Option<&Camera>,
|
||||
world: Option<&HitableList>,
|
||||
samples: u8,
|
||||
) {
|
||||
let &mut Chunk {
|
||||
x,
|
||||
y,
|
||||
nx,
|
||||
ny,
|
||||
start_x,
|
||||
start_y,
|
||||
ref mut buffer,
|
||||
} = chunk;
|
||||
let camera = camera.unwrap();
|
||||
let world = world.unwrap();
|
||||
|
||||
let mut rng = rand::thread_rng();
|
||||
let mut offset = 0;
|
||||
|
||||
for j in start_y..start_y + ny {
|
||||
for i in start_x..start_x + nx {
|
||||
let mut color = Vec3::new(0.0, 0.0, 0.0);
|
||||
for _s in 0..samples {
|
||||
let u = (i as f32 + rng.gen::<f32>()) / x as f32;
|
||||
let v = (j as f32 + rng.gen::<f32>()) / y as f32;
|
||||
|
||||
let ray = camera.get_ray(u, v);
|
||||
color += calc_color(ray, &world, 0);
|
||||
}
|
||||
|
||||
color /= samples as f32;
|
||||
|
||||
// gamma 2 corrected
|
||||
buffer[offset] = (255.99 * color.r().sqrt()) as u8;
|
||||
buffer[offset + 1] = (255.99 * color.g().sqrt()) as u8;
|
||||
buffer[offset + 2] = (255.99 * color.b().sqrt()) as u8;
|
||||
offset += 4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn calc_color(ray: Ray, world: &HitableList, depth: u32) -> Vec3 {
|
||||
if let Some(hit_rec) = world.hit(&ray, 0.001, std::f32::MAX) {
|
||||
if depth >= 50 {
|
||||
Vec3::new(0.0, 0.0, 0.0)
|
||||
} else {
|
||||
let material = hit_rec.material;
|
||||
if let (attenuation, Some(scattered_ray)) = material.scatter(&ray, &hit_rec) {
|
||||
calc_color(scattered_ray, &world, depth + 1) * attenuation
|
||||
} else {
|
||||
Vec3::new(0.0, 0.0, 0.0)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
let unit_direction = ray.direction().unit_vector();
|
||||
let t = 0.5 * (unit_direction.y() + 1.0);
|
||||
Vec3::new(1.0, 1.0, 1.0) * (1.0 - t) + Vec3::new(0.5, 0.7, 1.0) * t
|
||||
}
|
||||
}
|
109
src/main.rs
Normal file
109
src/main.rs
Normal file
|
@ -0,0 +1,109 @@
|
|||
#![allow(clippy::suspicious_arithmetic_impl)]
|
||||
#![feature(test)]
|
||||
extern crate test;
|
||||
|
||||
mod camera;
|
||||
mod demos;
|
||||
mod types;
|
||||
|
||||
pub use camera::Camera;
|
||||
|
||||
use {
|
||||
demos::Demo,
|
||||
sdl2::{
|
||||
event::{Event, WindowEvent},
|
||||
keyboard::Keycode,
|
||||
pixels::PixelFormatEnum,
|
||||
},
|
||||
std::time::Instant,
|
||||
};
|
||||
|
||||
const NUM_SAMPLES: u8 = 100;
|
||||
const VERTICAL_PARTITION: usize = 8;
|
||||
const HORIZONTAL_PARTITION: usize = 8;
|
||||
|
||||
fn main() -> Result<(), String> {
|
||||
let sdl_ctx = sdl2::init()?;
|
||||
let video_subsys = sdl_ctx.video()?;
|
||||
let (mut width, mut height): (usize, usize) = (2000, 1000);
|
||||
|
||||
let window = video_subsys
|
||||
.window("Ray tracing in a weekend", width as u32, height as u32)
|
||||
.position_centered()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let mut event_pump = sdl_ctx.event_pump()?;
|
||||
|
||||
let mut canvas = window
|
||||
.into_canvas()
|
||||
.target_texture()
|
||||
.build()
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
// RGBA framebuffer
|
||||
let mut buffer = vec![0; height * width * 4];
|
||||
|
||||
let texture_creator = canvas.texture_creator();
|
||||
let mut texture = texture_creator
|
||||
.create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
//println!("{:?} {:?} {:?}", texture.query(), texture.color_mod(), texture.alpha_mod());
|
||||
|
||||
let mut active_demo: &dyn Demo = &demos::MotionBlur;
|
||||
// TODO: Should update when window is unfocus since the project window retains
|
||||
// data from overlapped window
|
||||
// TODO: Maybe consider using condition variable to make loop {} not run at full
|
||||
// speed at all times pinning a core at 100%
|
||||
let mut should_update = true;
|
||||
|
||||
loop {
|
||||
for event in event_pump.poll_iter() {
|
||||
match event {
|
||||
Event::Quit { .. }
|
||||
| Event::KeyDown {
|
||||
keycode: Some(Keycode::Escape),
|
||||
..
|
||||
} => return Ok(()),
|
||||
Event::KeyUp { keycode, .. } => {
|
||||
match keycode {
|
||||
Some(Keycode::S) => {
|
||||
active_demo.save_as_ppm(&buffer, width, height);
|
||||
should_update = false;
|
||||
}
|
||||
None => unreachable!(),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
Event::Window {
|
||||
win_event: WindowEvent::Resized(w, h),
|
||||
..
|
||||
} => {
|
||||
width = w as usize;
|
||||
height = h as usize;
|
||||
buffer.resize(width * height * 4, 0);
|
||||
texture = texture_creator
|
||||
.create_texture_static(PixelFormatEnum::BGR888, width as u32, height as u32)
|
||||
.expect("error in resizing texture");
|
||||
should_update = true;
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
if should_update {
|
||||
let now = Instant::now();
|
||||
active_demo.render(&mut buffer, width, height, NUM_SAMPLES);
|
||||
println!(
|
||||
"Demo {} Time Taken(s) = {}",
|
||||
active_demo.name(),
|
||||
now.elapsed().as_secs_f32()
|
||||
);
|
||||
|
||||
texture.update(None, &buffer, width * 4).unwrap();
|
||||
canvas.copy(&texture, None, None).unwrap();
|
||||
canvas.present();
|
||||
should_update = false;
|
||||
}
|
||||
}
|
||||
}
|
31
src/types/hitable.rs
Normal file
31
src/types/hitable.rs
Normal file
|
@ -0,0 +1,31 @@
|
|||
use {
|
||||
crate::types::Vec3,
|
||||
crate::types::{Material, Ray},
|
||||
};
|
||||
|
||||
pub struct HitRecord<'a> {
|
||||
/// Rays are represented by A + t * B
|
||||
/// where A is the source point and B destination point
|
||||
/// by adjusting t we can move forward/back on the ray
|
||||
///
|
||||
/// t is the point at which a ray intersected another object.
|
||||
/// As in, If we put this value of t in A + t * B equation, We'll get the exact
|
||||
/// point at which a ray intersects some other object
|
||||
pub t: f32,
|
||||
/// Ray object otherwise is represented by the Source/Destination points
|
||||
/// p is what we get when we perform the operation, A + t * B
|
||||
/// i.e. A vector from Ray source to the point t
|
||||
pub p: Vec3,
|
||||
|
||||
/// unit outward facing normal
|
||||
pub normal: Vec3,
|
||||
|
||||
/// material if any of the surface
|
||||
pub material: &'a Box<dyn Material>,
|
||||
}
|
||||
|
||||
pub trait Hitable: Send + Sync {
|
||||
fn hit(&self, _ray: &Ray, _t_min: f32, _t_max: f32) -> Option<HitRecord> {
|
||||
None
|
||||
}
|
||||
}
|
25
src/types/hitable_list.rs
Normal file
25
src/types/hitable_list.rs
Normal file
|
@ -0,0 +1,25 @@
|
|||
use crate::types::{HitRecord, Hitable, Ray};
|
||||
|
||||
pub struct HitableList {
|
||||
pub list: Vec<Box<dyn Hitable>>,
|
||||
}
|
||||
|
||||
impl Hitable for HitableList {
|
||||
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let mut closest_so_far = t_max;
|
||||
let mut hit_rec: Option<HitRecord> = None;
|
||||
for obj in &self.list {
|
||||
if let Some(l_hit_rec) = obj.hit(ray, t_min, closest_so_far) {
|
||||
closest_so_far = l_hit_rec.t;
|
||||
hit_rec = Some(l_hit_rec);
|
||||
}
|
||||
}
|
||||
hit_rec
|
||||
}
|
||||
}
|
||||
|
||||
impl HitableList {
|
||||
pub fn push(&mut self, obj: Box<dyn Hitable>) {
|
||||
self.list.push(obj);
|
||||
}
|
||||
}
|
151
src/types/material.rs
Normal file
151
src/types/material.rs
Normal file
|
@ -0,0 +1,151 @@
|
|||
use {
|
||||
crate::types::{HitRecord, Ray, Vec3},
|
||||
rand::Rng,
|
||||
};
|
||||
|
||||
pub trait Material: Send + Sync {
|
||||
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>);
|
||||
}
|
||||
|
||||
pub struct Lambertian {
|
||||
albedo: Vec3,
|
||||
}
|
||||
|
||||
impl Lambertian {
|
||||
pub fn new(a: Vec3) -> Self {
|
||||
Self { albedo: a }
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Lambertian {
|
||||
fn scatter(&self, ray: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
|
||||
let mut rng = rand::thread_rng();
|
||||
let target = hit_rec.p + hit_rec.normal + random_point_in_unit_sphere(&mut rng);
|
||||
let scattered_ray = Ray::new(hit_rec.p, target - hit_rec.p, ray.time());
|
||||
|
||||
(self.albedo, Some(scattered_ray))
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Metal {
|
||||
albedo: Vec3,
|
||||
fuzz: f32,
|
||||
}
|
||||
|
||||
impl Metal {
|
||||
pub fn new(albedo: Vec3) -> Self {
|
||||
Self { albedo, fuzz: 0.0 }
|
||||
}
|
||||
pub fn with_fuzz(albedo: Vec3, fuzz: f32) -> Self {
|
||||
Self { albedo, fuzz }
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Metal {
|
||||
fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let reflected_ray = reflect(ray_in.direction().unit_vector(), hit_rec.normal);
|
||||
let scattered_ray = Ray::new(
|
||||
hit_rec.p,
|
||||
reflected_ray + random_point_in_unit_sphere(&mut rng) * self.fuzz,
|
||||
ray_in.time(),
|
||||
);
|
||||
|
||||
if scattered_ray.direction().dot(&hit_rec.normal) > 0.0 {
|
||||
(self.albedo, Some(scattered_ray))
|
||||
} else {
|
||||
(self.albedo, None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Dielectric {
|
||||
reflection_index: f32,
|
||||
}
|
||||
|
||||
impl Dielectric {
|
||||
pub fn new(reflection_index: f32) -> Self {
|
||||
Self { reflection_index }
|
||||
}
|
||||
}
|
||||
|
||||
impl Material for Dielectric {
|
||||
fn scatter(&self, ray_in: &Ray, hit_rec: &HitRecord) -> (Vec3, Option<Ray>) {
|
||||
let reflected_ray = reflect(ray_in.direction(), hit_rec.normal);
|
||||
// Glass absorbs nothing! So, Attenuation is always going to be 1.0 for this
|
||||
let attenuation = Vec3::new(1.0, 1.0, 1.0);
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
let (outward_normal, ni_over_nt, cosine) = if ray_in.direction().dot(&hit_rec.normal) > 0.0
|
||||
{
|
||||
(
|
||||
-hit_rec.normal,
|
||||
self.reflection_index,
|
||||
(ray_in.direction().dot(&hit_rec.normal) * self.reflection_index)
|
||||
/ ray_in.direction().length(),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
hit_rec.normal,
|
||||
1.0 / self.reflection_index,
|
||||
(-ray_in.direction().dot(&hit_rec.normal)) / ray_in.direction().length(),
|
||||
)
|
||||
};
|
||||
|
||||
if let Some(refracted_ray) = refract(ray_in.direction(), outward_normal, ni_over_nt) {
|
||||
let reflect_prob = schlick(cosine, self.reflection_index);
|
||||
|
||||
if rng.gen::<f32>() < reflect_prob {
|
||||
(
|
||||
attenuation,
|
||||
Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())),
|
||||
)
|
||||
} else {
|
||||
(
|
||||
attenuation,
|
||||
Some(Ray::new(hit_rec.p, refracted_ray, ray_in.time())),
|
||||
)
|
||||
}
|
||||
} else {
|
||||
(
|
||||
attenuation,
|
||||
Some(Ray::new(hit_rec.p, reflected_ray, ray_in.time())),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Christophe Schlick's Polynomial approximation to figure out reflectivity as the angle changes
|
||||
// See Fresnel Equations, https://en.wikipedia.org/wiki/Fresnel_equations
|
||||
fn schlick(cosine: f32, reflection_index: f32) -> f32 {
|
||||
let mut r0 = (1.0 - reflection_index) / (1.0 + reflection_index);
|
||||
r0 = r0 * r0;
|
||||
r0 + (1.0 - r0) * (1.0 - cosine).powf(5.0)
|
||||
}
|
||||
|
||||
fn reflect(incident: Vec3, normal: Vec3) -> Vec3 {
|
||||
incident - normal * incident.dot(&normal) * 2.0
|
||||
}
|
||||
|
||||
// Snell's Law
|
||||
fn refract(incident: Vec3, normal: Vec3, ni_over_nt: f32) -> Option<Vec3> {
|
||||
let uv = incident.unit_vector();
|
||||
let dt = uv.dot(&normal);
|
||||
let discriminant = 1.0 - ni_over_nt * ni_over_nt * (1.0 - dt * dt);
|
||||
if discriminant > 0.0 {
|
||||
Some((uv - normal * dt) * ni_over_nt - normal * discriminant.sqrt())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn random_point_in_unit_sphere(rng: &mut rand::rngs::ThreadRng) -> Vec3 {
|
||||
let mut point = Vec3::new(rng.gen::<f32>(), rng.gen::<f32>(), rng.gen::<f32>()) * 2.0
|
||||
- Vec3::new(1.0, 1.0, 1.0);
|
||||
while point.sq_len() >= 1.0 {
|
||||
point = Vec3::new(rng.gen::<f32>(), rng.gen::<f32>(), rng.gen::<f32>()) * 2.0
|
||||
- Vec3::new(1.0, 1.0, 1.0);
|
||||
}
|
||||
point
|
||||
}
|
15
src/types/mod.rs
Normal file
15
src/types/mod.rs
Normal file
|
@ -0,0 +1,15 @@
|
|||
mod hitable;
|
||||
mod hitable_list;
|
||||
pub mod material;
|
||||
mod moving_sphere;
|
||||
mod ray;
|
||||
mod sphere;
|
||||
mod vec3;
|
||||
|
||||
pub use hitable::{HitRecord, Hitable};
|
||||
pub use hitable_list::HitableList;
|
||||
pub use material::Material;
|
||||
pub use moving_sphere::MovingSphere;
|
||||
pub use ray::Ray;
|
||||
pub use sphere::Sphere;
|
||||
pub use vec3::Vec3;
|
72
src/types/moving_sphere.rs
Normal file
72
src/types/moving_sphere.rs
Normal file
|
@ -0,0 +1,72 @@
|
|||
use crate::types::{HitRecord, Hitable, Material, Ray, Vec3};
|
||||
|
||||
pub struct MovingSphere {
|
||||
radius: f32,
|
||||
center_start: Vec3,
|
||||
center_end: Vec3,
|
||||
time_start: f32,
|
||||
time_end: f32,
|
||||
material: Box<dyn Material>,
|
||||
}
|
||||
|
||||
impl MovingSphere {
|
||||
pub fn new(
|
||||
center_start: Vec3,
|
||||
center_end: Vec3,
|
||||
time_start: f32,
|
||||
time_end: f32,
|
||||
radius: f32,
|
||||
material: Box<dyn Material>,
|
||||
) -> Self {
|
||||
Self {
|
||||
center_start,
|
||||
center_end,
|
||||
time_start,
|
||||
time_end,
|
||||
radius,
|
||||
material,
|
||||
}
|
||||
}
|
||||
|
||||
fn center(&self, time: f32) -> Vec3 {
|
||||
self.center_start
|
||||
+ (self.center_end - self.center_start)
|
||||
* ((time - self.time_start) / (self.time_end - self.time_start))
|
||||
}
|
||||
}
|
||||
|
||||
impl Hitable for MovingSphere {
|
||||
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let oc = ray.origin() - self.center(ray.time());
|
||||
let a = ray.direction().dot(&ray.direction());
|
||||
let b = oc.dot(&ray.direction());
|
||||
let c = oc.dot(&oc) - self.radius * self.radius;
|
||||
|
||||
let discriminant = b * b - a * c;
|
||||
let discriminant_root = discriminant.sqrt();
|
||||
|
||||
if discriminant > 0.0 {
|
||||
let root = (-b - discriminant_root) / a;
|
||||
if root < t_max && root > t_min {
|
||||
let p = ray.point_at_parameter(root);
|
||||
return Some(HitRecord {
|
||||
t: root,
|
||||
p,
|
||||
normal: (p - self.center(ray.time())) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
let root = (-b + discriminant_root) / a;
|
||||
if root < t_max && root > t_min {
|
||||
let p = ray.point_at_parameter(root);
|
||||
return Some(HitRecord {
|
||||
t: root,
|
||||
p,
|
||||
normal: (p - self.center(ray.time())) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
29
src/types/ray.rs
Normal file
29
src/types/ray.rs
Normal file
|
@ -0,0 +1,29 @@
|
|||
use crate::types::Vec3;
|
||||
|
||||
pub struct Ray {
|
||||
a: Vec3,
|
||||
b: Vec3,
|
||||
time: f32,
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new(a: Vec3, b: Vec3, time: f32) -> Ray {
|
||||
Ray { a, b, time }
|
||||
}
|
||||
#[inline]
|
||||
pub const fn origin(&self) -> Vec3 {
|
||||
self.a
|
||||
}
|
||||
#[inline]
|
||||
pub const fn direction(&self) -> Vec3 {
|
||||
self.b
|
||||
}
|
||||
#[inline]
|
||||
pub fn point_at_parameter(&self, t: f32) -> Vec3 {
|
||||
self.a + self.b * t
|
||||
}
|
||||
#[inline]
|
||||
pub const fn time(&self) -> f32 {
|
||||
self.time
|
||||
}
|
||||
}
|
61
src/types/sphere.rs
Normal file
61
src/types/sphere.rs
Normal file
|
@ -0,0 +1,61 @@
|
|||
use crate::types::{HitRecord, Hitable, Material, Ray, Vec3};
|
||||
|
||||
pub struct Sphere {
|
||||
center: Vec3,
|
||||
radius: f32,
|
||||
material: Box<dyn Material>,
|
||||
}
|
||||
|
||||
impl Sphere {
|
||||
pub fn new(center: Vec3, radius: f32, material: Box<dyn Material>) -> Self {
|
||||
Self {
|
||||
center,
|
||||
radius,
|
||||
material,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hitable for Sphere {
|
||||
fn hit(&self, ray: &Ray, t_min: f32, t_max: f32) -> Option<HitRecord> {
|
||||
let oc = ray.origin() - self.center;
|
||||
let a = ray.direction().dot(&ray.direction());
|
||||
let b = oc.dot(&ray.direction());
|
||||
let c = oc.dot(&oc) - self.radius * self.radius;
|
||||
|
||||
// The discriminant is calculated using b^2 - 4 * a * c
|
||||
// but in this specific case, If we put the equation in the
|
||||
// formula to find quadratic roots, We can get this shorter
|
||||
// formula to find the discriminant.
|
||||
// Check this for detailed proof
|
||||
// https://vchizhov.github.io/resources/ray%20tracing/ray%20tracing%20tutorial%20series%20vchizhov/ray_casting/part1/intersecting_a_sphere.md.html#appendix
|
||||
let discriminant = b * b - a * c;
|
||||
let discriminant_root = discriminant.sqrt();
|
||||
|
||||
if discriminant > 0.0 {
|
||||
let root = (-b - discriminant_root) / a;
|
||||
if root < t_max && root > t_min {
|
||||
let p = ray.point_at_parameter(root);
|
||||
return Some(HitRecord {
|
||||
t: root,
|
||||
p,
|
||||
normal: (p - self.center) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
|
||||
let root = (-b + discriminant_root) / a;
|
||||
if root < t_max && root > t_min {
|
||||
let p = ray.point_at_parameter(root);
|
||||
|
||||
return Some(HitRecord {
|
||||
t: root,
|
||||
p,
|
||||
normal: (p - self.center) / self.radius,
|
||||
material: &self.material,
|
||||
});
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
192
src/types/vec3.rs
Normal file
192
src/types/vec3.rs
Normal file
|
@ -0,0 +1,192 @@
|
|||
use std::{
|
||||
fmt::{Display, Formatter, Result as FmtResult},
|
||||
ops::{Add, AddAssign, Div, DivAssign, Index, IndexMut, Mul, MulAssign, Neg, Sub, SubAssign},
|
||||
};
|
||||
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct Vec3([f32; 3]);
|
||||
|
||||
impl Vec3 {
|
||||
#[inline]
|
||||
pub const fn new(a: f32, b: f32, c: f32) -> Vec3 {
|
||||
Vec3([a, b, c])
|
||||
}
|
||||
#[inline]
|
||||
pub fn x(&self) -> f32 {
|
||||
self[0]
|
||||
}
|
||||
#[inline]
|
||||
pub fn y(&self) -> f32 {
|
||||
self[1]
|
||||
}
|
||||
#[inline]
|
||||
pub fn z(&self) -> f32 {
|
||||
self[2]
|
||||
}
|
||||
#[inline]
|
||||
pub fn r(&self) -> f32 {
|
||||
self[0]
|
||||
}
|
||||
#[inline]
|
||||
pub fn g(&self) -> f32 {
|
||||
self[1]
|
||||
}
|
||||
#[inline]
|
||||
pub fn b(&self) -> f32 {
|
||||
self[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn length(&self) -> f32 {
|
||||
self.sq_len().sqrt()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn sq_len(&self) -> f32 {
|
||||
self[0] * self[0] + self[1] * self[1] + self[2] * self[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn dot(&self, v: &Vec3) -> f32 {
|
||||
self[0] * v[0] + self[1] * v[1] + self[2] * v[2]
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn cross(&self, v: &Vec3) -> Vec3 {
|
||||
Vec3([
|
||||
self[1] * v[2] - self[2] * v[1],
|
||||
self[2] * v[0] - self[0] * v[2],
|
||||
self[0] * v[1] - self[1] * v[0],
|
||||
])
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn make_unit_vector(&mut self) {
|
||||
let k = 1.0f32 / (self[0] * self[0] + self[1] * self[1] + self[2] * self[2]);
|
||||
self[0] *= k;
|
||||
self[1] *= k;
|
||||
self[2] *= k;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn unit_vector(&self) -> Vec3 {
|
||||
let length = self.length();
|
||||
Vec3([self[0] / length, self[1] / length, self[2] / length])
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn add(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self[0] + o[0], self[1] + o[1], self[2] + o[2]])
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vec3 {
|
||||
fn add_assign(&mut self, o: Vec3) {
|
||||
self.0[0] += o.0[0];
|
||||
self.0[1] += o.0[1];
|
||||
self.0[2] += o.0[2];
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn sub(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self[0] - o[0], self[1] - o[1], self[2] - o[2]])
|
||||
}
|
||||
}
|
||||
|
||||
impl SubAssign for Vec3 {
|
||||
fn sub_assign(&mut self, o: Vec3) {
|
||||
self[0] -= o[0];
|
||||
self[1] -= o[1];
|
||||
self[2] -= o[2];
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn neg(self) -> Vec3 {
|
||||
Vec3([-self[0], -self[1], -self[2]])
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<Vec3> for Vec3 {
|
||||
fn mul_assign(&mut self, o: Vec3) {
|
||||
self[0] *= o[0];
|
||||
self[1] *= o[1];
|
||||
self[2] *= o[2];
|
||||
}
|
||||
}
|
||||
|
||||
impl MulAssign<f32> for Vec3 {
|
||||
fn mul_assign(&mut self, o: f32) {
|
||||
self[0] *= o;
|
||||
self[1] *= o;
|
||||
self[2] *= o;
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f32> for Vec3 {
|
||||
type Output = Vec3;
|
||||
fn mul(self, o: f32) -> Vec3 {
|
||||
Vec3([self[0] * o, self[1] * o, self[2] * o])
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
fn mul(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self[0] * o[0], self[1] * o[1], self[2] * o[2]])
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, o: Vec3) -> Vec3 {
|
||||
Vec3([self[0] / o[0], self[1] / o[1], self[2] / o[2]])
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f32> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, o: f32) -> Vec3 {
|
||||
let o = 1.0 / o;
|
||||
Vec3([self[0] * o, self[1] * o, self[2] * o])
|
||||
}
|
||||
}
|
||||
|
||||
impl DivAssign<f32> for Vec3 {
|
||||
fn div_assign(&mut self, o: f32) {
|
||||
let o = 1.0 / o;
|
||||
self.0[0] *= o;
|
||||
self.0[1] *= o;
|
||||
self.0[2] *= o;
|
||||
}
|
||||
}
|
||||
|
||||
impl Index<usize> for Vec3 {
|
||||
type Output = f32;
|
||||
|
||||
fn index(&self, q: usize) -> &f32 {
|
||||
&self.0[q]
|
||||
}
|
||||
}
|
||||
|
||||
impl IndexMut<usize> for Vec3 {
|
||||
fn index_mut(&mut self, q: usize) -> &mut f32 {
|
||||
&mut self.0[q]
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Vec3 {
|
||||
fn fmt(&self, f: &mut Formatter) -> FmtResult {
|
||||
f.write_fmt(format_args!("{} {} {}", self[0], self[1], self[2]))
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user