Simplifying the Ray-Sphere Intersection Code
Let’s revisit the ray-sphere function:
use code::{
color::{Color, write_color},
ray::Ray,
vec3::{Point3, Vec3, dot, unit_vector},
};
fn hit_sphere(center: Point3, radius: f64, r: Ray) -> Option<f64> {
let oc = center - r.origin();
let a = dot(r.direction(), r.direction());
let b = -2.0 * dot(r.direction(), oc);
let c = dot(oc, oc) - radius * radius;
let discriminant = b * b - 4.0 * a * c;
(discriminant >= 0.0).then(|| (-b - f64::sqrt(discriminant)) / (2.0 * a))
}
fn ray_color(r: Ray) -> Color {
if let Some(t) = hit_sphere(Point3::new(0.0, 0.0, -1.0), 0.5, r) {
let n = unit_vector(r.at(t) - Vec3::new(0.0, 0.0, -1.0));
return 0.5 * Color::new(n.x() + 1.0, n.y() + 1.0, n.z() + 1.0);
}
let unit_direction = unit_vector(r.direction());
let a = 0.5 * (unit_direction.y() + 1.0);
(1.0 - a) * Color::new(1.0, 1.0, 1.0) + a * Color::new(0.5, 0.7, 1.0)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Image
const ASPECT_RATIO: f64 = 16.0 / 9.0;
const IMAGE_WIDTH: i32 = 400;
// Calculate the image height, and ensure that it's at least 1.
const IMAGE_HEIGHT: i32 = {
let image_height = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as i32;
if image_height < 1 { 1 } else { image_height }
};
// Camera
let focal_length = 1.0;
let viewport_height = 2.0;
let viewport_width = viewport_height * (IMAGE_WIDTH as f64) / (IMAGE_HEIGHT as f64);
let camera_center = Point3::new(0.0, 0.0, 0.0);
// Calculate the vectors across the horizontal and down the vertical viewport edges.
let viewport_u = Vec3::new(viewport_width, 0.0, 0.0);
let viewport_v = Vec3::new(0.0, -viewport_height, 0.0);
// Calculate the horizontal and vertical delta vectors from pixel to pixel.
let pixel_delta_u = viewport_u / IMAGE_WIDTH as f64;
let pixel_delta_v = viewport_v / IMAGE_HEIGHT as f64;
// Calculate the location of the upper left pixel.
let viewport_upper_left =
camera_center - Vec3::new(0.0, 0.0, focal_length) - viewport_u / 2.0 - viewport_v / 2.0;
let pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);
// Render
env_logger::init();
println!("P3");
println!("{IMAGE_WIDTH} {IMAGE_HEIGHT}");
println!("255");
for j in 0..IMAGE_HEIGHT {
log::info!("Scanlines remaining: {}", IMAGE_HEIGHT - j);
for i in 0..IMAGE_WIDTH {
let pixel_center =
pixel00_loc + (i as f64) * pixel_delta_u + (j as f64) * pixel_delta_v;
let ray_direction = pixel_center - camera_center;
let r = Ray::new(camera_center, ray_direction);
let pixel_color = ray_color(r);
write_color(std::io::stdout(), pixel_color)?;
}
}
log::info!("Done.");
Ok(())
}
Listing 13: [main.rs] Ray-sphere intersection code (before)
First, recall that a vector dotted with itself is equal to the squared length of that vector.
Second, notice how the equation for \( b \) has a factor of negative two in it. Consider what happens to the quadratic equation if \( b = 2h \):
\[ \frac{-b \pm \sqrt{b^2 - 4ac}}{2a}\] \[= \frac{-(-2h) \pm \sqrt{(-2h)^2 - 4ac}}{2a}\] \[= \frac{2h \pm 2 \sqrt{h^2 - ac}}{2a}\] \[= \frac{h \pm \sqrt{h^2 - ac}}{a}\]
This simplifies nicely, so we'll use it. So solving for \( h \):
\[b = -2 \mathbf{d} \cdot (\mathbf{C} - \mathbf{Q}) \] \[b = -2h \] \[h = \frac{b}{-2} = \mathbf{d} \cdot (\mathbf{C} - \mathbf{Q}) \]
Using these observations, we can now simplify the sphere-intersection code to this:
diff --git a/src/main.rs b/src/main.rs
index 405ca4b..1f26e3d 100644
--- a/src/main.rs
+++ b/src/main.rs
@@ -1,82 +1,82 @@
use code::{
color::{Color, write_color},
ray::Ray,
vec3::{Point3, Vec3, dot, unit_vector},
};
fn hit_sphere(center: Point3, radius: f64, r: Ray) -> Option<f64> {
let oc = center - r.origin();
- let a = dot(r.direction(), r.direction());
- let b = -2.0 * dot(r.direction(), oc);
- let c = dot(oc, oc) - radius * radius;
- let discriminant = b * b - 4.0 * a * c;
+ let a = r.direction().length_squared();
+ let h = dot(r.direction(), oc);
+ let c = oc.length_squared() - radius * radius;
+ let discriminant = h * h - a * c;
- (discriminant >= 0.0).then(|| (-b - f64::sqrt(discriminant)) / (2.0 * a))
+ (discriminant >= 0.0).then(|| (h - f64::sqrt(discriminant)) / a)
}
fn ray_color(r: Ray) -> Color {
if let Some(t) = hit_sphere(Point3::new(0.0, 0.0, -1.0), 0.5, r) {
let n = unit_vector(r.at(t) - Vec3::new(0.0, 0.0, -1.0));
return 0.5 * Color::new(n.x() + 1.0, n.y() + 1.0, n.z() + 1.0);
}
let unit_direction = unit_vector(r.direction());
let a = 0.5 * (unit_direction.y() + 1.0);
(1.0 - a) * Color::new(1.0, 1.0, 1.0) + a * Color::new(0.5, 0.7, 1.0)
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Image
const ASPECT_RATIO: f64 = 16.0 / 9.0;
const IMAGE_WIDTH: i32 = 400;
// Calculate the image height, and ensure that it's at least 1.
const IMAGE_HEIGHT: i32 = {
let image_height = (IMAGE_WIDTH as f64 / ASPECT_RATIO) as i32;
if image_height < 1 { 1 } else { image_height }
};
// Camera
let focal_length = 1.0;
let viewport_height = 2.0;
let viewport_width = viewport_height * (IMAGE_WIDTH as f64) / (IMAGE_HEIGHT as f64);
let camera_center = Point3::new(0.0, 0.0, 0.0);
// Calculate the vectors across the horizontal and down the vertical viewport edges.
let viewport_u = Vec3::new(viewport_width, 0.0, 0.0);
let viewport_v = Vec3::new(0.0, -viewport_height, 0.0);
// Calculate the horizontal and vertical delta vectors from pixel to pixel.
let pixel_delta_u = viewport_u / IMAGE_WIDTH as f64;
let pixel_delta_v = viewport_v / IMAGE_HEIGHT as f64;
// Calculate the location of the upper left pixel.
let viewport_upper_left =
camera_center - Vec3::new(0.0, 0.0, focal_length) - viewport_u / 2.0 - viewport_v / 2.0;
let pixel00_loc = viewport_upper_left + 0.5 * (pixel_delta_u + pixel_delta_v);
// Render
env_logger::init();
println!("P3");
println!("{IMAGE_WIDTH} {IMAGE_HEIGHT}");
println!("255");
for j in 0..IMAGE_HEIGHT {
log::info!("Scanlines remaining: {}", IMAGE_HEIGHT - j);
for i in 0..IMAGE_WIDTH {
let pixel_center =
pixel00_loc + (i as f64) * pixel_delta_u + (j as f64) * pixel_delta_v;
let ray_direction = pixel_center - camera_center;
let r = Ray::new(camera_center, ray_direction);
let pixel_color = ray_color(r);
write_color(std::io::stdout(), pixel_color)?;
}
}
log::info!("Done.");
Ok(())
}
Listing 14: [main.rs] Ray-sphere intersection code (after)