Implementation of part 1. The green circle.
This commit is contained in:
60
bheisler/src/lib.rs
Normal file
60
bheisler/src/lib.rs
Normal file
@@ -0,0 +1,60 @@
|
||||
extern crate image;
|
||||
|
||||
pub mod point;
|
||||
pub mod rendering;
|
||||
pub mod scene;
|
||||
pub mod vector;
|
||||
|
||||
use image::GenericImage;
|
||||
use image::Pixel;
|
||||
use rendering::Intersectable;
|
||||
use scene::Scene;
|
||||
|
||||
pub fn render(scene: &Scene) -> image::DynamicImage {
|
||||
let mut image = image::DynamicImage::new_rgb8(scene.width, scene.height);
|
||||
let black = image::Rgba::from_channels(0, 0, 0, 0);
|
||||
for x in 0..scene.width {
|
||||
for y in 0..scene.height {
|
||||
let ray = rendering::Ray::create_prime(x, y, scene);
|
||||
|
||||
if scene.sphere.intersect(&ray) {
|
||||
let c = &scene.sphere.color;
|
||||
image.put_pixel(x, y, image::Rgba(c.to_rgba()))
|
||||
} else {
|
||||
image.put_pixel(x, y, black);
|
||||
}
|
||||
}
|
||||
}
|
||||
image
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_can_render_scene() {
|
||||
use point::Point;
|
||||
use rendering::Sphere;
|
||||
use scene::Color;
|
||||
|
||||
let scene = Scene {
|
||||
width: 800,
|
||||
height: 600,
|
||||
fov: 90.0,
|
||||
sphere: Sphere {
|
||||
center: Point {
|
||||
x: 0.0,
|
||||
y: 0.0,
|
||||
z: -5.0,
|
||||
},
|
||||
radius: 1.0,
|
||||
color: Color {
|
||||
red: 0.4,
|
||||
green: 1.0,
|
||||
blue: 0.4,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
let img: image::DynamicImage = render(&scene);
|
||||
assert_eq!(scene.width, img.width());
|
||||
assert_eq!(scene.height, img.height());
|
||||
img.save("/tmp/bheisler.png").unwrap();
|
||||
}
|
||||
3
bheisler/src/main.rs
Normal file
3
bheisler/src/main.rs
Normal file
@@ -0,0 +1,3 @@
|
||||
fn main() {
|
||||
println!("Hello, world!");
|
||||
}
|
||||
30
bheisler/src/point.rs
Normal file
30
bheisler/src/point.rs
Normal file
@@ -0,0 +1,30 @@
|
||||
use std::ops::Sub;
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct Point {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
impl Point {
|
||||
pub fn zero() -> Point {
|
||||
Point {
|
||||
x: 0.,
|
||||
y: 0.,
|
||||
z: 0.,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Sub for Point {
|
||||
type Output = Point;
|
||||
|
||||
fn sub(self, other: Point) -> Point {
|
||||
Point {
|
||||
x: self.x - other.x,
|
||||
y: self.y - other.y,
|
||||
z: self.z - other.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
53
bheisler/src/rendering.rs
Normal file
53
bheisler/src/rendering.rs
Normal file
@@ -0,0 +1,53 @@
|
||||
use point::Point;
|
||||
use scene::Color;
|
||||
use scene::Scene;
|
||||
use vector::Vector3;
|
||||
|
||||
pub trait Intersectable {
|
||||
fn intersect(&self, ray: &Ray) -> bool;
|
||||
}
|
||||
|
||||
pub struct Sphere {
|
||||
pub center: Point,
|
||||
pub radius: f32,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
impl Intersectable for Sphere {
|
||||
fn intersect(&self, ray: &Ray) -> bool {
|
||||
//Create a line segment between the ray origin and the center of the sphere
|
||||
let l: Vector3 = (self.center - ray.origin).into();
|
||||
//Use l as a hypotenuse and find the length of the adjacent side
|
||||
let adj2 = l.dot(&ray.direction);
|
||||
//Find the length-squared of the opposite side
|
||||
//This is equivalent to (but faster than) (l.length() * l.length()) - (adj2 * adj2)
|
||||
let d2 = l.dot(&l) - (adj2 * adj2);
|
||||
//If that length-squared is less than radius squared, the ray intersects the sphere
|
||||
d2 < (self.radius * self.radius)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Ray {
|
||||
pub origin: Point,
|
||||
pub direction: Vector3,
|
||||
}
|
||||
|
||||
impl Ray {
|
||||
pub fn create_prime(x: u32, y: u32, scene: &Scene) -> Ray {
|
||||
assert!(scene.width > scene.height);
|
||||
let fov_adjustment = (scene.fov.to_radians() / 2.0).tan();
|
||||
let aspect_ratio = (scene.width as f32) / (scene.height as f32);
|
||||
let sensor_x =
|
||||
((((x as f32 + 0.5) / scene.width as f32) * 2.0 - 1.0) * aspect_ratio) * fov_adjustment;
|
||||
let sensor_y = (1.0 - ((y as f32 + 0.5) / scene.height as f32) * 2.0) * fov_adjustment;
|
||||
|
||||
Ray {
|
||||
origin: Point::zero(),
|
||||
direction: Vector3 {
|
||||
x: sensor_x,
|
||||
y: sensor_y,
|
||||
z: -1.0,
|
||||
}.normalize(),
|
||||
}
|
||||
}
|
||||
}
|
||||
26
bheisler/src/scene.rs
Normal file
26
bheisler/src/scene.rs
Normal file
@@ -0,0 +1,26 @@
|
||||
use rendering::Sphere;
|
||||
|
||||
pub struct Color {
|
||||
pub red: f32,
|
||||
pub green: f32,
|
||||
pub blue: f32,
|
||||
}
|
||||
|
||||
impl Color {
|
||||
pub fn to_rgba(&self) -> [u8; 4] {
|
||||
let v: [u8; 4] = [
|
||||
(255. * self.red) as u8,
|
||||
(255. * self.green) as u8,
|
||||
(255. * self.blue) as u8,
|
||||
255u8,
|
||||
];
|
||||
v
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Scene {
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub fov: f32,
|
||||
pub sphere: Sphere,
|
||||
}
|
||||
48
bheisler/src/vector.rs
Normal file
48
bheisler/src/vector.rs
Normal file
@@ -0,0 +1,48 @@
|
||||
use point::Point;
|
||||
use std::ops::Div;
|
||||
|
||||
pub struct Vector3 {
|
||||
pub x: f32,
|
||||
pub y: f32,
|
||||
pub z: f32,
|
||||
}
|
||||
|
||||
impl Vector3 {
|
||||
pub fn normalize(&self) -> Vector3 {
|
||||
self / self.length()
|
||||
}
|
||||
|
||||
pub fn length_squared(&self) -> f32 {
|
||||
self.x * self.x + self.y * self.y + self.z * self.z
|
||||
}
|
||||
|
||||
pub fn length(&self) -> f32 {
|
||||
self.length_squared().sqrt()
|
||||
}
|
||||
|
||||
pub fn dot(&self, rhs: &Vector3) -> f32 {
|
||||
self.x * rhs.x + self.y * rhs.y + self.z * rhs.z
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Div<f32> for &'a Vector3 {
|
||||
type Output = Vector3;
|
||||
|
||||
fn div(self, rhs: f32) -> Vector3 {
|
||||
Vector3 {
|
||||
x: self.x / rhs,
|
||||
y: self.y / rhs,
|
||||
z: self.z / rhs,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Point> for Vector3 {
|
||||
fn from(p: Point) -> Self {
|
||||
Vector3 {
|
||||
x: p.x,
|
||||
y: p.y,
|
||||
z: p.z,
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user