Ray Tracing In One Weekend - Refraction issues - rust

I'm, currently working through Ray Tracing In One Weekend to get familiar with rust. Adding the dielectric material (glass) is causing me some headaches: My refraction isn't flipping upside down!
Here's the code I'm using for my Vector struct:
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());
let out_parallel = r * (*normal);
out_perp + out_paralle
}
}
And this is my scatter function for the material:
fn scatter(&self, ray: &Ray, hit_record: &HitRecord) -> Option<(Option<Ray>, Color)> {
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();
let color = Color::new(1.0, 1.0, 1.0);
if cannot_refract || reflectance > rng.gen::<f64>() {
let reflected = unit_direction.reflected(&hit_record.normal);
let scattered = Ray::new(hit_record.point, reflected);
Some((Some(scattered), color))
} else {
let direction = unit_direction.refract(&hit_record.normal, refraction_ratio);
let scattered = Ray::new(hit_record.point, direction);
Some((Some(scattered), color))
}
}
It sort of works if I negate x and y of the refract-result, but still looks obviously wrong. Additionally, if I go a few steps back in the book and implement the 100% refraction glass, my sphere's are solid black, and I have to negate the z axis to see anything at all. So something is amiss with my refraction code, but I can't figure it out
Full code at: https://phlaym.net/git/phlaym/rustracer/src/commit/89a2333644a82f2645e4ad554eadf7d4f142f2c0/src

In the method src/hittable.rs which checks if a sphere is hit, the c code looks like this.
// Find the nearest root that lies in the acceptable range.
auto root = (-half_b - sqrtd) / a;
if (root < t_min || t_max < root) {
root = (-half_b + sqrtd) / a;
if (root < t_min || t_max < root)
return false;
}
You have ported it to rust code with the following listing:
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;
}
}
The problem here is the second let root. You have created a new variable with its own scope for the inner brackets but not changed the already created variable defined before. To do this you have to make it mutable.
let mut root = (-half_b - sqrtd) / a;
if root < t_min || t_max < root {
root = (-half_b + sqrtd) / a;
if root < t_min || t_max < root {
return None;
}
}
Additionally I changed the following in src/ray.rs
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() }
};
to match the corresponding C code. Be aware of the unwrap used.
let scattered = rect.material.scatter(self, &rect);
if let Some((scattered_ray, albedo)) = scattered {
return albedo * scattered_ray.unwrap().pixel_color(world, depth - 1)
}
return Color::default()
And remove your tries to correct the reflections:
let reflected = Vec3::new(-reflected.x(), reflected.y(), -reflected.z());

Related

Use map object as function input

I have a function that calculates the variance of an iterator of floats, I would like to be able to call this function on an iterator after using the map method to transform it.
use num::Float;
fn compute_var_iter<'a, I, T>(vals: I) -> T
where
I: Iterator<Item = &'a T>,
T: 'a + Float + std::ops::AddAssign,
{
// online variance function
// Var = E[X^2] - E[X]^2
// corrects for 1/n -> 1/(n-1)
let mut x = T::zero();
let mut xsquare = T::zero();
let mut len = T::zero();
for &val in vals {
x += val;
xsquare += val * val;
len += T::one();
}
((xsquare / len) - (x / len) * (x / len)) / (len - T::one()) * len
}
fn main() {
let a: Vec<f64> = (1..100001).map(|i| i as f64).collect();
let b: Vec<f64> = (0..100000).map(|i| i as f64).collect();
dbg!(compute_var_iter(&mut a.iter())); // this works
dbg!(compute_var_iter(a.iter().zip(b).map(|(x, y)| x * y))); // this does not work
}
Is there a performant way to get the map output back to an iterator or to make the function take the map object as an input so that we can avoid having to .collect() and keep the execution lazy?
You can use the iterator objects directly without collect:
use num::Float;
fn compute_var_iter<I, T>(vals: I) -> T
where
I: Iterator<Item = T>,
T: Float + std::ops::AddAssign,
{
// online variance function
// Var = E[X^2] - E[X]^2
// corrects for 1/n -> 1/(n-1)
let mut x = T::zero();
let mut xsquare = T::zero();
let mut len = T::zero();
for val in vals {
x += val;
xsquare += val * val;
len += T::one();
}
((xsquare / len) - (x / len) * (x / len)) / (len - T::one()) * len
}
fn main() {
let a = (1..100001).map(|i| i as f64);
let b = (0..100000).map(|i| i as f64);
let c: Vec<f64> = (0..10000).map(|i| i as f64).collect();
dbg!(compute_var_iter(a.clone())); // this works
dbg!(compute_var_iter(c.iter().map(|i| *i))); // this works
dbg!(compute_var_iter(a.zip(b).map(|(x, y)| x * y)));
}
Playground
Notice that you would need to clone the iterator if you intend to use it several times. Also you do not really need to use references since numbers are usually Copy and the cost is the same as creating the references itself.

Why am I getting unexpected colors?

I am trying to create gradient blobs. I supply the gen function with a Vec<ColorPoint> and each point should have go from a maximum intensity at the center to no effect at the radius. The main problem is that I am not getting the colors I expect in the output image.
use image::{ImageBuffer, RgbImage, Rgb};
use std::ops::Add;
/// Color with red, green, blue and alpha channels between 0.0 and 1.0
#[derive(Debug)]
struct RgbaColor {
r: f64,
g: f64,
b: f64,
a: f64,
}
impl RgbaColor {
fn to_rgb(&self) -> Rgb<u8> {
let r = (&self.r*255.0) as u8;
let g = (&self.g*255.0) as u8;
let b = (&self.b*255.0) as u8;
Rgb::from([r, g, b])
}
}
impl Add for RgbaColor {
type Output = Self;
fn add(self, fg: Self) -> Self {
// self is the background and fg is the foreground
let new_alpha = fg.a + self.a * (1 - fg.a);
Self {
r: (fg.r * fg.a + self.r * self.a * (1.0 - fg.a)) / new_alpha,
g: (fg.g * fg.a + self.g * self.a * (1.0 - fg.a)) / new_alpha,
b: (fg.b * fg.a + self.b * self.a * (1.0 - fg.a)) / new_alpha,
a: new_alpha
}
}
}
#[derive(Debug)]
struct ColorPoint {
color: RgbaColor,
center: (u32, u32),
radius: u32,
}
impl ColorPoint {
fn rgba_color_at_point(&self, x: u32, y: u32) -> RgbaColor {
let x_dist = x as f64 - self.center.0 as f64;
let y_dist = y as f64 - self.center.1 as f64;
let dist = (x_dist.powf(2.0) + y_dist.powf(2.0)).sqrt();
RgbaColor {
r: self.color.r,
g: self.color.g,
b: self.color.b,
a: self.color.a * dist / self.radius as f64,
}
}
}
fn main() {
let color_points = vec![
ColorPoint{color: RgbaColor{r: 1.0, g:1.0, b:0.0, a:1.0}, center: (0, 50), radius: 20},
ColorPoint{color: RgbaColor{r: 1.0, g:0.0, b:0.0, a:1.0}, center: (10, 0), radius: 30}
];
gen(color_points)
}
fn gen(color_points: Vec<ColorPoint>) {
let geometry = (50, 100);
let mut background: RgbImage = ImageBuffer::new(geometry.0 as u32, geometry.1 as u32);
for (x, y, pixel) in background.enumerate_pixels_mut() {
let mut curr_color = RgbaColor{ r:0.0, g:0.0, b:0.0, a:1.0 }; // hardcoded background color
for color_point in color_points.iter() {
curr_color = curr_color + color_point.rgba_color_at_point(x,y);
}
*pixel = curr_color.to_rgb();
}
background.save("image.png").unwrap();
}
Output:
This code almost does what I expect although the position of the yellow and red blobs seem to have swapped and when I change the hard coded background color to white RgbaColor{ r:1.0, g:1.0, b:1.0, a:1.0 } I seem to have a magenta background.
I'm not sure whether my color model is wrong or if it's something else because when adding individual RgbaColors I get the correct colors.

Chaining iterators together to remove repeated code

The following code attempts to chain two iterators together.
fn main() {
let height = 3;
let width = 4;
let horizontal = (0..height).map(|row| {let rw = row * width; rw..rw + width});
horizontal.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
let vertical = (0..width).map(|col| (0..height).map(move |n| col + n * width));
vertical.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
let all = horizontal.chain(vertical);
//all.for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");});
}
But the compiler complains about mismatched types.
error[E0271]: type mismatch resolving `<Map<std::ops::Range<{integer}>, [closure#src/main.rs:6:35: 6:82]> as IntoIterator>::Item == std::ops::Range<{integer}>`
--> src/main.rs:8:26
|
8 | let all = horizontal.chain(vertical);
| ^^^^^ expected struct `Map`, found struct `std::ops::Range`
|
= note: expected type `Map<std::ops::Range<{integer}>, [closure#src/main.rs:6:57: 6:81]>`
found struct `std::ops::Range<{integer}>`
The signature of chain is:
fn chain<U>(self, other: U) -> Chain<Self, <U as IntoIterator>::IntoIter>ⓘ where
U: IntoIterator<Item = Self::Item>
Both iterators have as Item type an Iterator with the same Item type, which admittedly is not quite what the signature demands. But I can call for example .for_each(|x| { print!("("); x.for_each(|x|print!(" {:?} ", x)); println!(")");}) on each iterator, so why can't I construct the chain to call it on the chain? Is there another way to remove such code duplication?
It's because your types doesn't have the same Item:
.map(|row| {let rw = row * width; rw..rw + width});
.map(|col| (0..height).map(move |n| col + n * width))
One is a Range the other is a Map of a Range.
The solution is to use flatten() or on your case flat_map():
fn main() {
let height = 3;
let width = 4;
println!("Horizontal:");
let horizontal = (0..height).flat_map(|row| {
let rw = row * width;
rw..rw + width
});
for x in horizontal.clone() {
println!("{:?}", x);
}
println!("\nVertical:");
let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
for x in vertical.clone() {
println!("{:?}", x);
}
println!("\nAll:");
let all = horizontal.chain(vertical);
for x in all {
println!("{:?}", x);
}
}
This made both vertical and horizontal iterator have the same Item type. Also, I remove for_each() in my opinion it's make the code unclear as for loop are for side effect that is imperative paradigm and iterator chaining is functional paradigm.
Bonus:
fn print_my_iter(name: &str, iter: impl Iterator<Item = i32>) {
println!("{}:", name);
for x in iter {
println!("{:?}", x);
}
}
fn main() {
let height = 3;
let width = 4;
let horizontal = (0..height).flat_map(|row| {
let rw = row * width;
rw..rw + width
});
print_my_iter("Horizontal", horizontal.clone());
let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
print_my_iter("\nVertical", vertical.clone());
let all = horizontal.chain(vertical);
print_my_iter("\nAll", all);
}
Ultra bonus:
use std::io::{self, Write};
fn print_my_iter(name: &str, iter: impl Iterator<Item = i32>) -> Result<(), io::Error> {
let stdout = io::stdout();
let mut handle = stdout.lock();
writeln!(handle, "{}:", name)?;
iter.map(|x| writeln!(handle, "{:?}", x)).collect()
}
fn main() -> Result<(), io::Error> {
let height = 3;
let width = 4;
let horizontal = (0..height).flat_map(|row| {
let rw = row * width;
rw..rw + width
});
print_my_iter("Horizontal", horizontal.clone())?;
let vertical = (0..width).flat_map(|col| (0..height).map(move |n| col + n * width));
print_my_iter("\nVertical", vertical.clone())?;
let all = horizontal.chain(vertical);
print_my_iter("\nAll", all)?;
Ok(())
}

My test fails at "attempt to subtract with overflow"

use itertools::Itertools;
#[derive(Copy, Clone, PartialEq, Eq, PartialOrd, Ord)]
struct Runner {
sec: u16,
}
impl Runner {
fn from(v: (u8, u8, u8)) -> Runner {
Runner {
sec: v.0 as u16 * 3600 + v.1 as u16 * 60 + v.2 as u16
}
}
}
fn parse_runner(strg: &str) -> Vec<Runner> {
strg.split(", ")
.flat_map(|personal_result| personal_result.split('|'))
.map(|x| x.parse::<u8>().unwrap())
.tuples::<(_, _, _)>()
.map(|x| Runner::from(x))
.sorted()
.collect::<Vec<Runner>>()
}
fn parse_to_format(x: u16) -> String {
let h = x / 3600;
let m = (x - 3600)/60;
let s = x % 60;
format!("{:02}|{:02}|{:02}", h, m, s)
}
fn return_stats(runners: &[Runner]) -> String {
let range: u16 = runners.last().unwrap().sec - runners.first().unwrap().sec;
let average: u16 = runners.iter().map(|&r| r.sec).sum::<u16>()/(runners.len() as u16);
let median: u16 = if runners.len()%2 != 0 {
runners.get(runners.len()/2).unwrap().sec
} else {
runners.get(runners.len()/2).unwrap().sec/2 + runners.get((runners.len()/2) + 1).unwrap().sec/2
};
format!("Range: {} Average: {} Median: {}", parse_to_format(range), parse_to_format(average), parse_to_format(median))
}
fn stati(strg: &str) -> String {
let run_vec = parse_runner(strg);
return_stats(&run_vec)
}
I cant find the mistake I made with supposedly substraction to make my code pass the test. Basically I'm trying to start with a &str like "01|15|59, 1|47|6, 01|17|20, 1|32|34, 2|3|17" and end up with another one like "Range: 00|47|18 Average: 01|35|15 Median: 01|32|34"
Sorry in advance for my mistake if it is really stupid, I've been trying to fix it for quite a while
https://www.codewars.com/kata/55b3425df71c1201a800009c/train/rust
let m = (x - 3600) / 60;
As Peter mentioned, that will indeed overflow if x is less than 3600. A u16 can not be negative.
Using integer arithmetic, here's another way of formatting seconds to hh|mm|ss and does not experience overflow:
fn seconds_to_hhmmss(mut s: u64) -> String {
let h = s / 3600;
s -= h * 3600;
let m = s / 60;
s -= m * 60;
format!("{:02}|{:02}|{:02}", h, m, s)
}

RGB to YCbCr using SIMD vectors lose some data

I'm writing JPEG decoder/encoder in Rust and I have some problem with RGB ↔ YCbCr conversion.
My code:
use std::simd::f32x4;
fn clamp<T>(val: T, min: T, max: T) -> T
where T: PartialOrd {
if val < min { min }
else if max < val { max }
else { val }
}
// in oryginal code there are 2 methods, one for processors with SSE3 and for rest
// both do the same and give the same results
pub fn sum_f32x4(f32x4(a, b, c, d): f32x4) -> f32 {
a + b + c + d
}
pub fn rgb_to_ycbcr(r: u8, g: u8, b: u8) -> (u8, u8, u8) {
let rgb = f32x4(r as f32, g as f32, b as f32, 1.0);
let y = sum_f32x4(rgb * f32x4( 0.2990, 0.5870, 0.1140, 0.0));
let cb = sum_f32x4(rgb * f32x4(-0.1687, -0.3313, 0.5000, 128.0));
let cr = sum_f32x4(rgb * f32x4( 0.5000, -0.4187, -0.0813, 128.0));
(y as u8, cb as u8, cr as u8)
}
pub fn ycbcr_to_rgb(y: u8, cb: u8, cr: u8) -> (u8, u8, u8) {
let ycbcr = f32x4(y as f32, cb as f32 - 128.0f32, cr as f32 - 128.0f32, 0.0);
let r = sum_f32x4(ycbcr * f32x4(1.0, 0.00000, 1.40200, 0.0));
let g = sum_f32x4(ycbcr * f32x4(1.0, -0.34414, -0.71414, 0.0));
let b = sum_f32x4(ycbcr * f32x4(1.0, 1.77200, 0.00000, 0.0));
(clamp(r, 0., 255.) as u8, clamp(g, 0., 255.) as u8, clamp(b, 0., 255.) as u8)
}
fn main() {
assert_eq!(rgb_to_ycbcr( 0, 71, 171), ( 61, 189, 84));
// assert_eq!(rgb_to_ycbcr( 0, 71, 169), ( 61, 189, 84)); // will fail
// for some reason we always lose data on blue channel
assert_eq!(ycbcr_to_rgb( 61, 189, 84), ( 0, 71, 169));
}
For some reason booth tests (in comments) passes. I would rather expect that at least one of them will fail. Am I wrong? At least it should stop at some point, but when I change jpeg::color::utils::rgb_to_ycbcr(0, 71, 171) to jpeg::color::utils::rgb_to_ycbcr(0, 71, 169) then test fails as YCbCr value has changed, so I will lose my blue channel forever.
#dbaupp put the nail in the coffin with the suggestion to use round:
#![allow(unstable)]
use std::simd::{f32x4};
use std::num::Float;
fn clamp(val: f32) -> u8 {
if val < 0.0 { 0 }
else if val > 255.0 { 255 }
else { val.round() as u8 }
}
fn sum_f32x4(v: f32x4) -> f32 {
v.0 + v.1 + v.2 + v.3
}
pub fn rgb_to_ycbcr((r, g, b): (u8, u8, u8)) -> (u8, u8, u8) {
let rgb = f32x4(r as f32, g as f32, b as f32, 1.0);
let y = sum_f32x4(rgb * f32x4( 0.299000, 0.587000, 0.114000, 0.0));
let cb = sum_f32x4(rgb * f32x4(-0.168736, -0.331264, 0.500000, 128.0));
let cr = sum_f32x4(rgb * f32x4( 0.500000, -0.418688, -0.081312, 128.0));
(clamp(y), clamp(cb), clamp(cr))
}
pub fn ycbcr_to_rgb((y, cb, cr): (u8, u8, u8)) -> (u8, u8, u8) {
let ycbcr = f32x4(y as f32, cb as f32 - 128.0f32, cr as f32 - 128.0f32, 0.0);
let r = sum_f32x4(ycbcr * f32x4(1.0, 0.00000, 1.40200, 0.0));
let g = sum_f32x4(ycbcr * f32x4(1.0, -0.34414, -0.71414, 0.0));
let b = sum_f32x4(ycbcr * f32x4(1.0, 1.77200, 0.00000, 0.0));
(clamp(r), clamp(g), clamp(b))
}
fn main() {
let mut rgb = (0, 71, 16);
println!("{:?}", rgb);
for _ in 0..100 {
let yuv = rgb_to_ycbcr(rgb);
rgb = ycbcr_to_rgb(yuv);
println!("{:?}", rgb);
}
}
Note that I also increased the precision of your values in rgb_to_ycbcr from the Wikipedia page. I also clamp in both functions, as well as calling round. Now the output is:
(0u8, 71u8, 16u8)
(1u8, 72u8, 16u8)
(1u8, 72u8, 16u8)
With the last value repeating for the entire loop.

Resources