Fix the weird other reflection
This commit is contained in:
parent
79b8faa00f
commit
75d4e6266f
8 changed files with 238 additions and 112 deletions
39
assignment-1d/Cargo.lock
generated
39
assignment-1d/Cargo.lock
generated
|
@ -52,6 +52,7 @@ dependencies = [
|
||||||
"rand",
|
"rand",
|
||||||
"rayon",
|
"rayon",
|
||||||
"tracing",
|
"tracing",
|
||||||
|
"tracing-appender",
|
||||||
"tracing-subscriber",
|
"tracing-subscriber",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
@ -785,6 +786,33 @@ dependencies = [
|
||||||
"once_cell",
|
"once_cell",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time"
|
||||||
|
version = "0.3.20"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "cd0cbfecb4d19b5ea75bb31ad904eb5b9fa13f21079c3b92017ebdf4999a5890"
|
||||||
|
dependencies = [
|
||||||
|
"itoa",
|
||||||
|
"serde",
|
||||||
|
"time-core",
|
||||||
|
"time-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-core"
|
||||||
|
version = "0.1.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "2e153e1f1acaef8acc537e68b44906d2db6436e2b35ac2c6b42640fff91f00fd"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "time-macros"
|
||||||
|
version = "0.2.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "fd80a657e71da814b8e5d60d3374fc6d35045062245d80224748ae522dd76f36"
|
||||||
|
dependencies = [
|
||||||
|
"time-core",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing"
|
name = "tracing"
|
||||||
version = "0.1.37"
|
version = "0.1.37"
|
||||||
|
@ -797,6 +825,17 @@ dependencies = [
|
||||||
"tracing-core",
|
"tracing-core",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tracing-appender"
|
||||||
|
version = "0.2.2"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "09d48f71a791638519505cefafe162606f706c25592e4bde4d97600c0195312e"
|
||||||
|
dependencies = [
|
||||||
|
"crossbeam-channel",
|
||||||
|
"time",
|
||||||
|
"tracing-subscriber",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "tracing-attributes"
|
name = "tracing-attributes"
|
||||||
version = "0.1.23"
|
version = "0.1.23"
|
||||||
|
|
|
@ -32,4 +32,5 @@ ordered-float = "3.4.0"
|
||||||
rand = "0.8.5"
|
rand = "0.8.5"
|
||||||
rayon = "1.6.1"
|
rayon = "1.6.1"
|
||||||
tracing = "0.1.37"
|
tracing = "0.1.37"
|
||||||
|
tracing-appender = "0.2.2"
|
||||||
tracing-subscriber = { version = "0.3.16", features = ["json"] }
|
tracing-subscriber = { version = "0.3.16", features = ["json"] }
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
#[macro_use]
|
#[macro_use]
|
||||||
|
extern crate anyhow;
|
||||||
|
#[macro_use]
|
||||||
extern crate tracing;
|
extern crate tracing;
|
||||||
|
|
||||||
use std::fs::File;
|
|
||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
use std::{fs::File, str::FromStr};
|
||||||
|
|
||||||
use anyhow::Result;
|
use anyhow::Result;
|
||||||
use assignment_1d::{image::Image, ray::Ray, scene::Scene};
|
use assignment_1d::{image::Image, ray::Ray, scene::Scene};
|
||||||
|
@ -11,6 +13,7 @@ use clap::{ArgAction, Parser};
|
||||||
|
|
||||||
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
use rayon::prelude::{IntoParallelIterator, ParallelIterator};
|
||||||
use tracing::metadata::LevelFilter;
|
use tracing::metadata::LevelFilter;
|
||||||
|
use tracing_appender::non_blocking::WorkerGuard;
|
||||||
use tracing_subscriber::{
|
use tracing_subscriber::{
|
||||||
fmt::Layer, prelude::__tracing_subscriber_SubscriberExt,
|
fmt::Layer, prelude::__tracing_subscriber_SubscriberExt,
|
||||||
util::SubscriberInitExt,
|
util::SubscriberInitExt,
|
||||||
|
@ -29,6 +32,14 @@ struct Opt {
|
||||||
#[clap(short = 'o', long = "output")]
|
#[clap(short = 'o', long = "output")]
|
||||||
output_path: Option<PathBuf>,
|
output_path: Option<PathBuf>,
|
||||||
|
|
||||||
|
/// Log output in json
|
||||||
|
#[clap(long = "json")]
|
||||||
|
use_json: bool,
|
||||||
|
|
||||||
|
/// Which file to send logs to (stderr by default)
|
||||||
|
#[clap(long = "log-output")]
|
||||||
|
log_output: Option<PathBuf>,
|
||||||
|
|
||||||
/// Force parallel projection to be used
|
/// Force parallel projection to be used
|
||||||
#[clap(long = "parallel")]
|
#[clap(long = "parallel")]
|
||||||
force_parallel: bool,
|
force_parallel: bool,
|
||||||
|
@ -40,30 +51,16 @@ struct Opt {
|
||||||
/// Verbosity
|
/// Verbosity
|
||||||
#[clap(short, long, action = ArgAction::Count)]
|
#[clap(short, long, action = ArgAction::Count)]
|
||||||
verbosity: u8,
|
verbosity: u8,
|
||||||
|
|
||||||
|
/// Evaluate at a single pixel
|
||||||
|
#[clap(short, long = "render-pixel")]
|
||||||
|
render_pixel: Option<RenderPixel>,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
let opt = Opt::parse();
|
let opt = Opt::parse();
|
||||||
|
|
||||||
let level_filter = match opt.verbosity {
|
let _guard = setup_logging(&opt);
|
||||||
0 => LevelFilter::ERROR,
|
|
||||||
1 => LevelFilter::WARN,
|
|
||||||
2 => LevelFilter::INFO,
|
|
||||||
3 => LevelFilter::DEBUG,
|
|
||||||
_ => LevelFilter::TRACE,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Set up logging
|
|
||||||
let layer = Layer::default()
|
|
||||||
.json()
|
|
||||||
.with_target(false)
|
|
||||||
.with_timer(tracing_subscriber::fmt::time::uptime())
|
|
||||||
.with_level(true);
|
|
||||||
|
|
||||||
tracing_subscriber::registry()
|
|
||||||
.with(layer)
|
|
||||||
.with(level_filter)
|
|
||||||
.init();
|
|
||||||
|
|
||||||
// Rename the output file if it's not provided
|
// Rename the output file if it's not provided
|
||||||
let out_file = opt
|
let out_file = opt
|
||||||
|
@ -81,15 +78,7 @@ fn main() -> Result<()> {
|
||||||
// Translate image pixels to real-world 3d coords
|
// Translate image pixels to real-world 3d coords
|
||||||
let translate_pixel = scene.pixel_translation_function(distance);
|
let translate_pixel = scene.pixel_translation_function(distance);
|
||||||
|
|
||||||
// Generate a parallel iterator for pixels
|
let evaluate_at_pixel = |px, py| {
|
||||||
// The iterator preserves order and uses row-major order
|
|
||||||
let pixels_iter = (0..scene.image_height)
|
|
||||||
.into_par_iter()
|
|
||||||
.flat_map(|y| (0..scene.image_width).into_par_iter().map(move |x| (x, y)));
|
|
||||||
|
|
||||||
// Loop through every single pixel of the output file
|
|
||||||
let pixels = pixels_iter
|
|
||||||
.map(|(px, py)| {
|
|
||||||
let span = trace_span!("main_loop", px = px, py = py);
|
let span = trace_span!("main_loop", px = px, py = py);
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
|
|
||||||
|
@ -111,7 +100,24 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
// let res= rayon::spawn(|| scene.trace_single_ray(ray, 0));
|
// let res= rayon::spawn(|| scene.trace_single_ray(ray, 0));
|
||||||
scene.trace_single_ray(scene.eye_pos, ray, 0)
|
scene.trace_single_ray(scene.eye_pos, ray, 0)
|
||||||
})
|
};
|
||||||
|
|
||||||
|
// For debugging purposes!
|
||||||
|
if let Some(RenderPixel(px, py)) = opt.render_pixel {
|
||||||
|
let pixel_color = evaluate_at_pixel(px, py)?;
|
||||||
|
println!("Pixel color: {pixel_color}");
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate a parallel iterator for pixels
|
||||||
|
// The iterator preserves order and uses row-major order
|
||||||
|
let pixels_iter = (0..scene.image_height)
|
||||||
|
.into_par_iter()
|
||||||
|
.flat_map(|y| (0..scene.image_width).into_par_iter().map(move |x| (x, y)));
|
||||||
|
|
||||||
|
// Loop through every single pixel of the output file
|
||||||
|
let pixels = pixels_iter
|
||||||
|
.map(|(px, py)| evaluate_at_pixel(px, py))
|
||||||
.collect::<Result<Vec<_>>>()?;
|
.collect::<Result<Vec<_>>>()?;
|
||||||
|
|
||||||
// Construct and emit image
|
// Construct and emit image
|
||||||
|
@ -128,3 +134,71 @@ fn main() -> Result<()> {
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct RenderPixel(usize, usize);
|
||||||
|
|
||||||
|
impl FromStr for RenderPixel {
|
||||||
|
type Err = anyhow::Error;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
let parts = s.split(",").collect::<Vec<_>>();
|
||||||
|
ensure!(parts.len() == 2, "must be a pair");
|
||||||
|
|
||||||
|
let x = parts[0].parse::<usize>()?;
|
||||||
|
let y = parts[1].parse::<usize>()?;
|
||||||
|
|
||||||
|
Ok(RenderPixel(x, y))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A little bit of engineering to make it easy to write conditional builders
|
||||||
|
/// for logging setup because the tracing-subscriber crate for some reason
|
||||||
|
/// decided it would be a good idea to have all of its builders be polymorphic?
|
||||||
|
macro_rules! logsetup_if {
|
||||||
|
($ident:ident , $cond:expr , $iftrue:expr , $iffalse:expr => { $($body:tt)* }) => {
|
||||||
|
if ($cond) {
|
||||||
|
let $ident = $iftrue;
|
||||||
|
$($body)*
|
||||||
|
} else {
|
||||||
|
let $ident = $iffalse;
|
||||||
|
$($body)*
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn setup_logging(opt: &Opt) -> Option<WorkerGuard> {
|
||||||
|
let mut result = None;
|
||||||
|
|
||||||
|
let level_filter = match opt.verbosity {
|
||||||
|
0 => LevelFilter::ERROR,
|
||||||
|
1 => LevelFilter::WARN,
|
||||||
|
2 => LevelFilter::INFO,
|
||||||
|
3 => LevelFilter::DEBUG,
|
||||||
|
_ => LevelFilter::TRACE,
|
||||||
|
};
|
||||||
|
|
||||||
|
let layer = Layer::default();
|
||||||
|
|
||||||
|
logsetup_if! (layer, opt.use_json, layer.json(), layer => {
|
||||||
|
let layer = layer
|
||||||
|
.with_target(false)
|
||||||
|
.with_timer(tracing_subscriber::fmt::time::uptime())
|
||||||
|
.with_level(true);
|
||||||
|
|
||||||
|
logsetup_if! (layer, opt.log_output.is_some(), {
|
||||||
|
let log_output = opt.log_output.clone().unwrap();
|
||||||
|
let file_appender = tracing_appender::rolling::never(".", log_output);
|
||||||
|
let (non_blocking, guard) = tracing_appender::non_blocking(file_appender);
|
||||||
|
result = Some(guard);
|
||||||
|
layer.with_writer(non_blocking)
|
||||||
|
}, layer => {
|
||||||
|
tracing_subscriber::registry()
|
||||||
|
.with(layer)
|
||||||
|
.with(level_filter)
|
||||||
|
.init();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
result
|
||||||
|
}
|
||||||
|
|
|
@ -1,15 +1,34 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use crate::{Point, Vector};
|
use crate::{Point, Vector};
|
||||||
|
|
||||||
/// A normalized parametric Ray of the form (origin + direction * time)
|
/// A normalized parametric Ray of the form (origin + direction * time)
|
||||||
///
|
///
|
||||||
/// That means at any time t: f64, the point represented by origin + direction *
|
/// That means at any time t: f64, the point represented by origin + direction *
|
||||||
/// time occurs on the ray.
|
/// time occurs on the ray. This is pretty much a (time -> point) function.
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
|
/// The point in space where the ray started
|
||||||
pub origin: Point,
|
pub origin: Point,
|
||||||
|
|
||||||
|
/// The direction the ray is headed
|
||||||
pub direction: Vector,
|
pub direction: Vector,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Ray {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(
|
||||||
|
f,
|
||||||
|
"({:.2}, {:.2}, {:.2}) + t * ({:.2}, {:.2}, {:.2})",
|
||||||
|
self.origin.x,
|
||||||
|
self.origin.y,
|
||||||
|
self.origin.z,
|
||||||
|
self.direction.x,
|
||||||
|
self.direction.y,
|
||||||
|
self.direction.z,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Ray {
|
impl Ray {
|
||||||
pub fn new(origin: Point, direction: Vector) -> Self {
|
pub fn new(origin: Point, direction: Vector) -> Self {
|
||||||
Ray { origin, direction }
|
Ray { origin, direction }
|
||||||
|
|
|
@ -11,7 +11,9 @@ use rayon::prelude::{
|
||||||
use crate::{
|
use crate::{
|
||||||
image::Color,
|
image::Color,
|
||||||
ray::Ray,
|
ray::Ray,
|
||||||
utils::{compute_refraction_lengths, dot, RefractionResult},
|
utils::{
|
||||||
|
compute_reflection_ray, compute_refraction_lengths, dot, RefractionResult,
|
||||||
|
},
|
||||||
Point, Vector,
|
Point, Vector,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -48,8 +50,7 @@ impl Scene {
|
||||||
intersection_context: IntersectionContext,
|
intersection_context: IntersectionContext,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
) -> Result<Color> {
|
) -> Result<Color> {
|
||||||
let span =
|
let span = trace_span!("compute_pixel_color", intersection = ?intersection_context, incident_ray=?incident_ray);
|
||||||
trace_span!("compute_pixel_color", intersection = ?intersection_context);
|
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
|
|
||||||
let material = match self.materials.get(object.material_idx) {
|
let material = match self.materials.get(object.material_idx) {
|
||||||
|
@ -83,17 +84,7 @@ impl Scene {
|
||||||
// The vector pointing in the direction of the light
|
// The vector pointing in the direction of the light
|
||||||
let light_direction = light.direction_from(intersection_context.point);
|
let light_direction = light.direction_from(intersection_context.point);
|
||||||
|
|
||||||
let normal = {
|
let normal = intersection_context.normal.normalize(); // reflection_normal();
|
||||||
let mut normal = intersection_context.normal.normalize();
|
|
||||||
|
|
||||||
// If we're exiting the material, the normal should face the other direction since that's
|
|
||||||
// how the reflection works
|
|
||||||
if intersection_context.exiting {
|
|
||||||
normal = -normal;
|
|
||||||
}
|
|
||||||
|
|
||||||
normal
|
|
||||||
};
|
|
||||||
|
|
||||||
// Viewer direction is no longer towards the eye, but to the last origin point, so that
|
// Viewer direction is no longer towards the eye, but to the last origin point, so that
|
||||||
// transmitted rays reflect properly
|
// transmitted rays reflect properly
|
||||||
|
@ -147,30 +138,6 @@ impl Scene {
|
||||||
})
|
})
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
/*
|
|
||||||
let specular_reflection: Color = {
|
|
||||||
let reflection_ray = self.compute_reflection_ray(
|
|
||||||
incident_ray.direction,
|
|
||||||
intersection_context.normal,
|
|
||||||
);
|
|
||||||
|
|
||||||
let fresnel_coefficient = self.compute_fresnel_coefficient(
|
|
||||||
&material,
|
|
||||||
incident_ray.direction,
|
|
||||||
intersection_context.normal,
|
|
||||||
);
|
|
||||||
|
|
||||||
// Jitter a bit to reduce acne
|
|
||||||
let origin = intersection_context.point;
|
|
||||||
let origin = origin + JITTER_CONST * reflection_ray;
|
|
||||||
|
|
||||||
let ray = Ray::new(origin, reflection_ray);
|
|
||||||
let r_lambda = self.trace_single_ray(origin, ray, depth + 1)?;
|
|
||||||
|
|
||||||
fresnel_coefficient * r_lambda
|
|
||||||
};
|
|
||||||
*/
|
|
||||||
|
|
||||||
let (eta_i, eta_t) = match intersection_context.exiting {
|
let (eta_i, eta_t) = match intersection_context.exiting {
|
||||||
// true => (material.eta, 1.0),
|
// true => (material.eta, 1.0),
|
||||||
_ => (1.0, material.eta),
|
_ => (1.0, material.eta),
|
||||||
|
@ -186,7 +153,7 @@ impl Scene {
|
||||||
)?
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
let transparency_component = if eta_t < 1.0 {
|
let transparency_component = if eta_t < 1.0 || material.alpha == 1.0 {
|
||||||
ZERO_COLOR
|
ZERO_COLOR
|
||||||
} else {
|
} else {
|
||||||
self.compute_transparency(
|
self.compute_transparency(
|
||||||
|
@ -212,6 +179,14 @@ impl Scene {
|
||||||
* (1.0 - material.alpha)
|
* (1.0 - material.alpha)
|
||||||
* transparency_component
|
* transparency_component
|
||||||
};
|
};
|
||||||
|
debug!(
|
||||||
|
last_time_component = ?(ambient_component + diffuse_and_specular),
|
||||||
|
?specular_reflection_component,
|
||||||
|
?transparency_component,
|
||||||
|
?fresnel_coefficient,
|
||||||
|
?color,
|
||||||
|
"color result"
|
||||||
|
);
|
||||||
|
|
||||||
// Apply depth cueing to the result
|
// Apply depth cueing to the result
|
||||||
let a_dc = {
|
let a_dc = {
|
||||||
|
@ -412,30 +387,15 @@ impl Scene {
|
||||||
fr
|
fr
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compute_reflection_ray(
|
|
||||||
&self,
|
|
||||||
incident_ray: Vector,
|
|
||||||
normal: Vector,
|
|
||||||
) -> Vector {
|
|
||||||
let opposite_incident_ray = (-incident_ray).normalize();
|
|
||||||
let unit_normal = normal.normalize();
|
|
||||||
|
|
||||||
let a = dot(unit_normal, opposite_incident_ray);
|
|
||||||
let r = 2.0 * (a * unit_normal) - opposite_incident_ray;
|
|
||||||
|
|
||||||
r
|
|
||||||
}
|
|
||||||
|
|
||||||
fn compute_specular_reflection(
|
fn compute_specular_reflection(
|
||||||
&self,
|
&self,
|
||||||
intersection_context: &IntersectionContext,
|
intersection_context: &IntersectionContext,
|
||||||
incident_ray: &Ray,
|
incident_ray: &Ray,
|
||||||
depth: usize,
|
depth: usize,
|
||||||
) -> Result<Color> {
|
) -> Result<Color> {
|
||||||
// Specular reflection
|
let reflection_ray = compute_reflection_ray(
|
||||||
let reflection_ray = self.compute_reflection_ray(
|
|
||||||
incident_ray.direction.clone(),
|
incident_ray.direction.clone(),
|
||||||
intersection_context.normal,
|
intersection_context.reflection_normal().normalize(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let origin = intersection_context.point;
|
let origin = intersection_context.point;
|
||||||
|
@ -458,14 +418,7 @@ impl Scene {
|
||||||
let _enter = span.enter();
|
let _enter = span.enter();
|
||||||
|
|
||||||
// Fix the normal direction to account for exiting a material
|
// Fix the normal direction to account for exiting a material
|
||||||
let normal = {
|
let normal = intersection_context.reflection_normal().normalize();
|
||||||
let mut n = intersection_context.normal.normalize();
|
|
||||||
if intersection_context.exiting {
|
|
||||||
n = -n;
|
|
||||||
}
|
|
||||||
|
|
||||||
n
|
|
||||||
};
|
|
||||||
|
|
||||||
let i = incident_ray.direction.normalize();
|
let i = incident_ray.direction.normalize();
|
||||||
|
|
||||||
|
@ -484,7 +437,7 @@ impl Scene {
|
||||||
match compute_refraction_lengths(normal, &incident_ray, eta_i, eta_t) {
|
match compute_refraction_lengths(normal, &incident_ray, eta_i, eta_t) {
|
||||||
Some(RefractionResult {
|
Some(RefractionResult {
|
||||||
cos_theta_i,
|
cos_theta_i,
|
||||||
sin_theta_i,
|
sin_theta_i: _,
|
||||||
sin_theta_t,
|
sin_theta_t,
|
||||||
cos_theta_t,
|
cos_theta_t,
|
||||||
}) => {
|
}) => {
|
||||||
|
@ -493,7 +446,7 @@ impl Scene {
|
||||||
// new direction.
|
// new direction.
|
||||||
|
|
||||||
// Calculate refraction direction
|
// Calculate refraction direction
|
||||||
let a = normal.normalize() * cos_theta_t;
|
let a = normal * cos_theta_t;
|
||||||
let s_direction = cos_theta_i * normal - i;
|
let s_direction = cos_theta_i * normal - i;
|
||||||
let m_unit = s_direction.normalize();
|
let m_unit = s_direction.normalize();
|
||||||
let b = m_unit * sin_theta_t;
|
let b = m_unit * sin_theta_t;
|
||||||
|
@ -550,4 +503,13 @@ pub struct IntersectionContext {
|
||||||
|
|
||||||
impl Eq for IntersectionContext {}
|
impl Eq for IntersectionContext {}
|
||||||
|
|
||||||
impl IntersectionContext {}
|
impl IntersectionContext {
|
||||||
|
// If we're exiting the material, the normal should face the other direction
|
||||||
|
// since that's how the reflection works
|
||||||
|
pub fn reflection_normal(&self) -> Vector {
|
||||||
|
match self.exiting {
|
||||||
|
true => -self.normal,
|
||||||
|
false => self.normal,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -44,6 +44,8 @@ impl Scene {
|
||||||
let earliest_intersection =
|
let earliest_intersection =
|
||||||
intersections.into_iter().min_by_key(|(_, t, _)| t.time);
|
intersections.into_iter().min_by_key(|(_, t, _)| t.time);
|
||||||
|
|
||||||
|
info!("Ray {ray:?} intersected at: {earliest_intersection:?}");
|
||||||
|
|
||||||
Ok(match earliest_intersection {
|
Ok(match earliest_intersection {
|
||||||
// Take the object's material color
|
// Take the object's material color
|
||||||
Some((obj_idx, intersection_context, object)) => self
|
Some((obj_idx, intersection_context, object)) => self
|
||||||
|
|
|
@ -61,6 +61,11 @@ impl Triangle {
|
||||||
-(a * x0 + b * y0 + c * z0 + d) / denom
|
-(a * x0 + b * y0 + c * z0 + d) / denom
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Intersected the plane behind where the ray started
|
||||||
|
if time < 0.0 {
|
||||||
|
return Ok(None);
|
||||||
|
}
|
||||||
|
|
||||||
let time = NotNan::new(time)?;
|
let time = NotNan::new(time)?;
|
||||||
let point = ray.eval(*time);
|
let point = ray.eval(*time);
|
||||||
|
|
||||||
|
|
|
@ -134,3 +134,27 @@ pub fn compute_refraction_lengths(
|
||||||
cos_theta_t,
|
cos_theta_t,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn compute_reflection_ray(incident_ray: Vector, normal: Vector) -> Vector {
|
||||||
|
let I = (-incident_ray).normalize();
|
||||||
|
let N = normal.normalize();
|
||||||
|
|
||||||
|
2.0 * dot(N, I) * N - I
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use crate::{utils::compute_reflection_ray, Vector};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_reflection_ray() {
|
||||||
|
let incident_ray = Vector::new(2.0, -1.0, 2.0);
|
||||||
|
let normal = Vector::new(0.0, 1.0, 0.0);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
compute_reflection_ray(incident_ray, normal),
|
||||||
|
Vector::new(2.0, 1.0, 2.0).normalize()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue