Initial
This commit is contained in:
commit
a86dbb516c
4
.gitignore
vendored
Normal file
4
.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
||||
/target
|
||||
*.ppm
|
||||
.idea
|
||||
*.iml
|
75
Cargo.lock
generated
Normal file
75
Cargo.lock
generated
Normal file
@ -0,0 +1,75 @@
|
||||
# This file is automatically @generated by Cargo.
|
||||
# It is not intended for manual editing.
|
||||
version = 3
|
||||
|
||||
[[package]]
|
||||
name = "cfg-if"
|
||||
version = "1.0.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
|
||||
|
||||
[[package]]
|
||||
name = "getrandom"
|
||||
version = "0.2.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4eb1a864a501629691edf6c15a593b7a51eebaa1e8468e9ddc623de7c9b58ec6"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"libc",
|
||||
"wasi",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "libc"
|
||||
version = "0.2.126"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "349d5a591cd28b49e1d1037471617a32ddcda5731b99419008085f72d5a53836"
|
||||
|
||||
[[package]]
|
||||
name = "ppv-lite86"
|
||||
version = "0.2.16"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "eb9f9e6e233e5c4a35559a617bf40a4ec447db2e84c20b55a6f83167b7e57872"
|
||||
|
||||
[[package]]
|
||||
name = "rand"
|
||||
version = "0.8.5"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"rand_chacha",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_chacha"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88"
|
||||
dependencies = [
|
||||
"ppv-lite86",
|
||||
"rand_core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rand_core"
|
||||
version = "0.6.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "d34f1408f55294453790c48b2f1ebbb1c5b4b7563eb1f418bcfcfdbb06ebb4e7"
|
||||
dependencies = [
|
||||
"getrandom",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "rustracer"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"rand",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wasi"
|
||||
version = "0.11.0+wasi-snapshot-preview1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
|
9
Cargo.toml
Normal file
9
Cargo.toml
Normal file
@ -0,0 +1,9 @@
|
||||
[package]
|
||||
name = "rustracer"
|
||||
version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
rand = "0.8.5"
|
45
src/camera.rs
Normal file
45
src/camera.rs
Normal file
@ -0,0 +1,45 @@
|
||||
use crate::{Point3, Ray, Vec3};
|
||||
|
||||
pub struct Camera {
|
||||
aspect_ratio: f64,
|
||||
viewport_height: f64,
|
||||
viewport_width: f64,
|
||||
focal_length: f64,
|
||||
origin: Point3,
|
||||
horizontal: Vec3,
|
||||
vertical: Vec3,
|
||||
lower_left_corner: Vec3
|
||||
}
|
||||
|
||||
impl Camera {
|
||||
pub fn get_ray(&self, u: f64, v: f64) -> Ray {
|
||||
Ray::new(
|
||||
self.origin,
|
||||
self.lower_left_corner + u*self.horizontal + v*self.vertical - self.origin)
|
||||
}
|
||||
pub fn new(aspect_ratio: f64) -> Self {
|
||||
let viewport_height = 2.0;
|
||||
let viewport_width = aspect_ratio * viewport_height;
|
||||
let origin = Point3::default();
|
||||
let horizontal = Vec3::new(viewport_width, 0.0, 0.0);
|
||||
let vertical = Vec3::new(0.0, viewport_height, 0.0);
|
||||
let focal_length = 1.0;
|
||||
Camera {
|
||||
aspect_ratio,
|
||||
viewport_height,
|
||||
viewport_width,
|
||||
focal_length,
|
||||
origin,
|
||||
horizontal,
|
||||
vertical,
|
||||
lower_left_corner: origin - horizontal/2.0 - vertical/2.0 - Vec3::new(0.0, 0.0, focal_length)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Camera {
|
||||
fn default() -> Self {
|
||||
let aspect_ratio = 16.0 / 9.0;
|
||||
Camera::new(aspect_ratio)
|
||||
}
|
||||
}
|
64
src/hittable.rs
Normal file
64
src/hittable.rs
Normal file
@ -0,0 +1,64 @@
|
||||
use crate::{Point3, Ray, Vec3};
|
||||
use crate::material::{Material};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct HitRecord<'material> {
|
||||
pub point: Point3,
|
||||
pub normal: Vec3,
|
||||
pub t: f64,
|
||||
pub front_face: bool,
|
||||
pub material: &'material Material
|
||||
}
|
||||
|
||||
pub trait Hittable {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
|
||||
}
|
||||
pub struct Sphere {
|
||||
pub center: Point3,
|
||||
pub radius: f64,
|
||||
pub material: Material
|
||||
}
|
||||
impl Sphere {
|
||||
pub fn new(center: Point3, radius: f64, material: Material) -> Sphere {
|
||||
Sphere { center, radius, material }
|
||||
}
|
||||
}
|
||||
impl Default for Sphere {
|
||||
fn default() -> Self {
|
||||
Sphere {
|
||||
center: Point3::default(),
|
||||
radius: 0.0,
|
||||
material: Material::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Hittable for Sphere {
|
||||
fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
|
||||
let oc = &ray.origin() - &self.center;
|
||||
let a = ray.direction().length_squared();
|
||||
let half_b = oc.dot(&ray.direction());
|
||||
let c = oc.length_squared() - &self.radius * &self.radius;
|
||||
let discriminant = half_b * half_b - a * c;
|
||||
if discriminant < 0.0 { return None; }
|
||||
|
||||
let sqrtd = f64::sqrt(discriminant);
|
||||
let root = (-half_b - sqrtd) / a;
|
||||
if root < t_min || t_max < root {
|
||||
let root = (-half_b + sqrtd) / a;
|
||||
if root < t_min || t_max < root { return None; }
|
||||
}
|
||||
|
||||
let point = ray.at(root);
|
||||
let normal = (point - self.center) / self.radius;
|
||||
let front_face = ray.direction().dot(&normal) < 0.0;
|
||||
let normal = if front_face { normal } else { -normal };
|
||||
Some(HitRecord {
|
||||
point,
|
||||
normal,
|
||||
t: root,
|
||||
front_face,
|
||||
material: &self.material
|
||||
})
|
||||
}
|
||||
}
|
81
src/main.rs
Normal file
81
src/main.rs
Normal file
@ -0,0 +1,81 @@
|
||||
use crate::camera::Camera;
|
||||
use crate::hittable::{Hittable, Sphere};
|
||||
use crate::ray::Ray;
|
||||
use crate::vec3::{Color, Point3, Vec3};
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
use crate::material::{Dielectric, Lambertian, Material, Metal};
|
||||
|
||||
mod vec3;
|
||||
mod ray;
|
||||
mod hittable;
|
||||
mod camera;
|
||||
mod material;
|
||||
|
||||
fn main() {
|
||||
// Image
|
||||
const ASPECT_RATIO: f64 = 16.0 / 9.0;
|
||||
const IMAGE_WIDTH: i32 = 400;
|
||||
const IMAGE_HEIGHT: i32 = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as i32;
|
||||
const SAMPLES_PER_PIXEL: i32 = 10;
|
||||
const MAX_DEPTH: i32 = 50;
|
||||
|
||||
// Camera
|
||||
let cam = Camera::new(ASPECT_RATIO);
|
||||
|
||||
// World
|
||||
let mut world:Vec<Box<dyn Hittable>> = Vec::new();
|
||||
|
||||
let center = Box::new(Sphere {
|
||||
center: Point3::new(0.0, 0.0, -1.0),
|
||||
radius: 0.5,
|
||||
material: Material::Lambertian(Lambertian::new(Color::new(0.7, 0.3, 0.3)))
|
||||
});
|
||||
let center_glass = Box::new(Sphere {
|
||||
center: Point3::new(0.0, 0.0, -1.0),
|
||||
radius: 0.5,
|
||||
material: Material::Dielectric(Dielectric::new(1.5))
|
||||
});
|
||||
world.push(center_glass);
|
||||
let ground = Sphere {
|
||||
center: Point3::new(0.0, -100.5, -1.0),
|
||||
radius: 100.0,
|
||||
material: Material::Lambertian(Lambertian::new(Color::new(0.8, 0.8, 0.0)))
|
||||
};
|
||||
world.push(Box::new(ground));
|
||||
let left = Box::new(Sphere {
|
||||
center: Point3::new(-1.0, 0.0, -1.0),
|
||||
radius: 0.5,
|
||||
material: Material::Metal(Metal::new(Color::new(0.8, 0.8, 0.8), 0.3))
|
||||
});
|
||||
let left_glass = Box::new(Sphere {
|
||||
center: Point3::new(-1.0, 0.0, -1.0),
|
||||
radius: 0.5,
|
||||
material: Material::Dielectric(Dielectric::new(1.5))
|
||||
});
|
||||
world.push(left_glass);
|
||||
let right = Box::new(Sphere {
|
||||
center: Point3::new(1.0, 0.0, -1.0),
|
||||
radius: 0.5,
|
||||
material: Material::Metal(Metal::new(Color::new(0.8, 0.6, 0.2), 1.0))
|
||||
});
|
||||
world.push(right);
|
||||
|
||||
println!("P3\n{} {}\n255", IMAGE_WIDTH, IMAGE_HEIGHT);
|
||||
let between = Uniform::from(0.0..1.0);
|
||||
let mut rng = rand::thread_rng();
|
||||
for j in (0..IMAGE_HEIGHT).rev() {
|
||||
eprint!("\rScanlines remaining: {} ", j);
|
||||
for i in 0..IMAGE_WIDTH {
|
||||
let mut color = Color::default();
|
||||
for s in 0..SAMPLES_PER_PIXEL {
|
||||
let random_number = between.sample(&mut rng);
|
||||
let u = (i as f64 + random_number) / (IMAGE_WIDTH - 1) as f64;
|
||||
let v = (j as f64 + random_number) / (IMAGE_HEIGHT - 1) as f64;
|
||||
let ray = cam.get_ray(u, v);
|
||||
color += ray.pixel_color(&world, MAX_DEPTH);
|
||||
}
|
||||
color.write_color(SAMPLES_PER_PIXEL);
|
||||
}
|
||||
}
|
||||
eprintln!("\nDone");
|
||||
}
|
124
src/material.rs
Normal file
124
src/material.rs
Normal file
@ -0,0 +1,124 @@
|
||||
use rand::Rng;
|
||||
use crate::hittable::HitRecord;
|
||||
use crate::{Color, Ray, Vec3};
|
||||
|
||||
pub trait Scatterable {
|
||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)>;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum Material {
|
||||
Lambertian(Lambertian),
|
||||
Metal(Metal),
|
||||
Dielectric(Dielectric)
|
||||
}
|
||||
|
||||
impl Default for Material {
|
||||
fn default() -> Self {
|
||||
Material::Lambertian(Lambertian::new(Color::default()))
|
||||
}
|
||||
}
|
||||
|
||||
impl Scatterable for Material {
|
||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||
match self {
|
||||
Material::Lambertian(l) => l.scatter(ray, hit_record),
|
||||
Material::Metal(m) => m.scatter(ray, hit_record),
|
||||
Material::Dielectric(d) => d.scatter(ray, hit_record)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Lambertian {
|
||||
pub albedo: Color
|
||||
}
|
||||
impl Lambertian {
|
||||
pub fn new(albedo: Color) -> Self {
|
||||
Lambertian { albedo }
|
||||
}
|
||||
}
|
||||
|
||||
impl Scatterable for Lambertian {
|
||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||
let mut direction = hit_record.normal + Vec3::random_unit_vector();
|
||||
if direction.near_zero() {
|
||||
direction = hit_record.normal;
|
||||
}
|
||||
let scattered = Ray::new(hit_record.point, direction);
|
||||
Some((Some(scattered), self.albedo))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Metal {
|
||||
pub albedo: Color,
|
||||
pub fuzz: f64
|
||||
}
|
||||
|
||||
impl Metal {
|
||||
pub fn new(albedo: Color, fuzz: f64) -> Self {
|
||||
Metal {
|
||||
albedo,
|
||||
fuzz: if fuzz < 1.0 { fuzz} else { 1.0 }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Scatterable for Metal {
|
||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||
let reflected = ray.direction().unit_vector().reflected(&hit_record.normal);
|
||||
let scattered = Ray::new(
|
||||
hit_record.point,
|
||||
reflected + self.fuzz * Vec3::random_in_unit_sphere());
|
||||
if scattered.direction().dot(&hit_record.normal) > 0.0 {
|
||||
Some((Some(scattered), self.albedo))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Dielectric { // Glass
|
||||
pub index_of_refraction: f64
|
||||
}
|
||||
|
||||
impl Dielectric {
|
||||
pub fn new(index_of_refraction: f64) -> Self {
|
||||
Dielectric { index_of_refraction }
|
||||
}
|
||||
fn reflectance(cosine: f64, ref_idx: f64) -> f64 {
|
||||
let r0 = (1.0-ref_idx) / (1.0+ref_idx);
|
||||
let r0 = r0*r0;
|
||||
r0 + (1.0-r0)*((1.0-cosine).powi(5))
|
||||
}
|
||||
}
|
||||
|
||||
impl Scatterable for Dielectric {
|
||||
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
|
||||
let color = Color::new(1.0, 1.0, 1.0);
|
||||
let refraction_ratio = if hit_record.front_face {
|
||||
1.0/self.index_of_refraction
|
||||
} else {
|
||||
self.index_of_refraction
|
||||
};
|
||||
let unit_direction = ray.direction().unit_vector();
|
||||
let cos_theta = ((-unit_direction).dot(&hit_record.normal)).min(1.0);
|
||||
let sin_theta = (1.0 - cos_theta*cos_theta).sqrt();
|
||||
let cannot_refract = refraction_ratio * sin_theta > 1.0;
|
||||
let reflectance = Dielectric::reflectance(cos_theta, refraction_ratio);
|
||||
let mut rng = rand::thread_rng();
|
||||
|
||||
if cannot_refract || reflectance > rng.gen::<f64>() {
|
||||
let reflected = unit_direction.reflected(&hit_record.normal);
|
||||
let reflected = Vec3::new(-reflected.x(), reflected.y(), -reflected.z());
|
||||
let scattered = Ray::new(hit_record.point, reflected);
|
||||
Some((Some(scattered), color))
|
||||
} else {
|
||||
let direction = unit_direction.refract_orig(&hit_record.normal, refraction_ratio);
|
||||
let scattered = Ray::new(hit_record.point, direction);
|
||||
Some((Some(scattered), color))
|
||||
}
|
||||
}
|
||||
}
|
66
src/ray.rs
Normal file
66
src/ray.rs
Normal file
@ -0,0 +1,66 @@
|
||||
use crate::{Color, Hittable, Vec3};
|
||||
use crate::hittable::HitRecord;
|
||||
use crate::material::Scatterable;
|
||||
use crate::vec3::Point3;
|
||||
|
||||
pub struct Ray {
|
||||
origin: Point3,
|
||||
direction: Vec3
|
||||
}
|
||||
impl Default for Ray {
|
||||
fn default() -> Self {
|
||||
Ray::new(Vec3::default(), Vec3::default())
|
||||
}
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn new(origin: Point3, direction: Point3) -> Ray {
|
||||
Ray { origin, direction }
|
||||
}
|
||||
pub fn at(&self, t: f64) -> Point3 {
|
||||
self.origin + self.direction * t
|
||||
}
|
||||
pub fn direction(&self) -> Vec3 { self.direction }
|
||||
pub fn origin(&self) -> Point3 { self.origin }
|
||||
pub fn pixel_color(&self, world: &Vec<Box<dyn Hittable>>, depth: i32) -> Color {
|
||||
if depth <= 0 {
|
||||
return Color::default();
|
||||
}
|
||||
if let Some(rect) = self.hit_world(world, 0.001, f64::INFINITY) {
|
||||
let scattered = rect.material.scatter(self, &rect);
|
||||
return match scattered {
|
||||
Some((scattered_ray, albedo)) => {
|
||||
match scattered_ray {
|
||||
Some(sr) => {
|
||||
albedo * sr.pixel_color(world, depth-1)
|
||||
},
|
||||
None => albedo
|
||||
}
|
||||
},
|
||||
None => { return Color::default() }
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
//Hot nothing, display sky color
|
||||
let unit_direction = self.direction().unit_vector();
|
||||
let t = 0.5 * (unit_direction.y() + 1.0);
|
||||
(1.0 - t) * Color::new(1.0, 1.0, 1.0) + t * Color::new(0.5, 0.7, 1.0)
|
||||
}
|
||||
fn hit_world<'material>(
|
||||
&self,
|
||||
world: &'material Vec<Box<dyn Hittable>>,
|
||||
t_min: f64,
|
||||
t_max: f64,
|
||||
) -> Option<HitRecord<'material>> {
|
||||
let mut closest_so_far = t_max;
|
||||
let mut hit_record = None;
|
||||
for sphere in world {
|
||||
if let Some(hit) = sphere.hit(self, t_min, closest_so_far) {
|
||||
closest_so_far = hit.t;
|
||||
hit_record = Some(hit);
|
||||
}
|
||||
}
|
||||
hit_record
|
||||
}
|
||||
}
|
253
src/vec3.rs
Normal file
253
src/vec3.rs
Normal file
@ -0,0 +1,253 @@
|
||||
use std::fmt::{Display, Formatter};
|
||||
use std::ops::{Add, AddAssign, Div, Mul, Neg, Sub};
|
||||
use rand::distributions::{Distribution, Uniform};
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub struct Vec3 { x: f64, y: f64, z: f64 }
|
||||
|
||||
pub type Point3 = Vec3;
|
||||
pub type Color = Vec3;
|
||||
|
||||
impl Vec3 {
|
||||
pub fn new(x: f64, y: f64, z: f64) -> Vec3 { Vec3 {x, y, z} }
|
||||
pub fn x(&self) -> f64 { self.x }
|
||||
pub fn y(&self) -> f64 { self.y }
|
||||
pub fn z(&self) -> f64 { self.z }
|
||||
pub fn length_squared(&self) -> f64 {
|
||||
self.x * self.x + self.y * self.y + self.z * self.z
|
||||
}
|
||||
pub fn length(&self) -> f64 { self.distance(&Vec3::default()) }
|
||||
pub fn unit_vector(&self) -> Vec3 {
|
||||
let length = self.length();
|
||||
Vec3::new(self.x / length, self.y / length, self.z / length)
|
||||
}
|
||||
pub fn dot(&self, v:&Vec3) -> f64 {
|
||||
self.x * v.x + self.y * v.y + self.z * v.z
|
||||
}
|
||||
pub fn cross(&self, v:&Vec3) -> Vec3 {
|
||||
Vec3::new(
|
||||
self.y * v.z - self.z * v.y,
|
||||
self.z * v.x - self.x * v.z,
|
||||
self.x * v.y - self.y * v.x
|
||||
)
|
||||
}
|
||||
pub fn distance(&self, other: &Vec3) -> f64 {
|
||||
let dx = self.x - other.x();
|
||||
let dy = self.y - other.y();
|
||||
let dz = self.z - other.z();
|
||||
(dx * dx + dy * dy + dz * dz).sqrt()
|
||||
}
|
||||
|
||||
pub fn random(min: f64, max:f64) -> Self {
|
||||
let between = Uniform::from(min..max);
|
||||
let mut rng = rand::thread_rng();
|
||||
Vec3::new(
|
||||
between.sample(&mut rng),
|
||||
between.sample(&mut rng),
|
||||
between.sample(&mut rng))
|
||||
}
|
||||
pub fn random_in_unit_sphere() -> Self {
|
||||
loop {
|
||||
let v = Vec3::random(-1.0, 1.0);
|
||||
if v.length_squared() < 1.0 {
|
||||
return v;
|
||||
}
|
||||
}
|
||||
}
|
||||
pub fn random_in_hemisphere(normal: &Vec3) -> Self {
|
||||
let vec = Vec3::random_in_unit_sphere();
|
||||
if vec.dot(normal) > 0.0 {
|
||||
vec
|
||||
} else {
|
||||
-vec
|
||||
}
|
||||
}
|
||||
pub fn random_unit_vector() -> Self { Vec3::random_in_unit_sphere().unit_vector() }
|
||||
pub fn near_zero(&self) -> bool {
|
||||
const MAXIMUM_DISTANCE_FROM_ZERO:f64 = 1e-8;
|
||||
self.x.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
|
||||
self.y.abs() < MAXIMUM_DISTANCE_FROM_ZERO &&
|
||||
self.z.abs() < MAXIMUM_DISTANCE_FROM_ZERO
|
||||
}
|
||||
pub fn reflected(&self, normal: &Vec3) -> Vec3 {
|
||||
let dp = self.dot(normal);
|
||||
let dp = dp * 2.0 * (*normal);
|
||||
*self - dp
|
||||
}
|
||||
pub fn refract(&self, normal: &Vec3, etai_over_etat: f64) -> Vec3 {
|
||||
let dot = (-(*self)).dot(normal);
|
||||
let cos_theta = dot.min(1.0);
|
||||
let out_perp = etai_over_etat * ((*self) + cos_theta * (*normal));
|
||||
let inner = 1.0 - out_perp.length_squared();
|
||||
let abs = inner.abs();
|
||||
let r = -(abs.sqrt());
|
||||
out_parallel = r * (*normal);
|
||||
out_perp + out_parallel
|
||||
}
|
||||
pub fn refract_sort_of_works(&self, normal: &Vec3, etai_over_etat: f64) -> Vec3 {
|
||||
let dot = (-(*self)).dot(normal);
|
||||
let cos_theta = dot.min(1.0);
|
||||
let out_perp = etai_over_etat * ((*self) + cos_theta * (*normal));
|
||||
let inner = 1.0 - out_perp.length_squared();
|
||||
let abs = inner.abs();
|
||||
let r = -(abs.sqrt());
|
||||
let out_parallel = r * (*normal);
|
||||
// Why?
|
||||
let mut res = out_perp + out_parallel;
|
||||
res.x = -res.x;
|
||||
//res.y = -res.y;
|
||||
res.z = -res.z;
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_refract() {
|
||||
let uv = Point3::new(1.0, 1.0, 0.0);
|
||||
let n = Point3::new(-1.0, 0.0, 0.0);
|
||||
let etai_over_etat = 1.0;
|
||||
let expected = Point3::new(0.0, 1.0, 0.0);
|
||||
let actual = uv.refract_orig( &n, etai_over_etat);
|
||||
assert_eq!(actual, expected);
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn write_color(self: Color, samples_per_pixel: i32) {
|
||||
let scale = 1.0 / samples_per_pixel as f64;
|
||||
let r = f64::sqrt(scale * self.x);
|
||||
let g = f64::sqrt(scale * self.y);
|
||||
let b = f64::sqrt(scale * self.z);
|
||||
let r = 256.0 * f64::clamp(r, 0.0, 0.999);
|
||||
let g = 256.0 * f64::clamp(g, 0.0, 0.999);
|
||||
let b = 256.0 * f64::clamp(b, 0.0, 0.999);
|
||||
println!("{} {} {}", r as i32, g as i32, b as i32);
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for Vec3 {
|
||||
fn default() -> Self { Vec3 {x: 0.0, y: 0.0, z: 0.0} }
|
||||
}
|
||||
|
||||
impl Display for Vec3 {
|
||||
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{} {} {}", self.x, self.y, self.z)
|
||||
}
|
||||
}
|
||||
|
||||
impl Add for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn add(self, other: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x + other.x(),
|
||||
y: self.y + other.y(),
|
||||
z: self.z + other.z(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl AddAssign for Vec3 {
|
||||
fn add_assign(&mut self, rhs: Self) {
|
||||
self.x += rhs.x;
|
||||
self.y += rhs.y;
|
||||
self.z += rhs.z;
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn sub(self, other: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x - other.x(),
|
||||
y: self.y - other.y(),
|
||||
z: self.z - other.z(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for &Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn sub(self, other: &Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x - other.x(),
|
||||
y: self.y - other.y(),
|
||||
z: self.z - other.z(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Neg for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn neg(self) -> Vec3 {
|
||||
Vec3 {
|
||||
x: -self.x,
|
||||
y: -self.y,
|
||||
z: -self.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn mul(self, other: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x * other.x(),
|
||||
y: self.y * other.y(),
|
||||
z: self.z * other.z(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<f64> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn mul(self, other: f64) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x * other,
|
||||
y: self.y * other,
|
||||
z: self.z * other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<Vec3> for f64 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn mul(self, other: Vec3) -> Vec3 {
|
||||
other * self
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Vec3> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, other: Vec3) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x / other.x(),
|
||||
y: self.y / other.y(),
|
||||
z: self.z / other.z(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<f64> for Vec3 {
|
||||
type Output = Vec3;
|
||||
|
||||
fn div(self, other: f64) -> Vec3 {
|
||||
Vec3 {
|
||||
x: self.x / other,
|
||||
y: self.y / other,
|
||||
z: self.z / other,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for Vec3 {
|
||||
fn eq(&self, other: &Vec3) -> bool {
|
||||
self.x == other.x() && self.y == other.y() && self.z == other.z()
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user