rustray/src/main.rs

232 lines
5.3 KiB
Rust
Raw Normal View History

2019-03-14 18:43:23 +00:00
use cgmath::prelude::*;
use cgmath::{vec3, vec4, InnerSpace, Matrix4, Point3, Vector3};
use image::ImageBuffer;
use utilities::prelude::*;
use std::f32::{consts::PI as M_PI, MAX};
struct Triangle {
points: [Vector3<f32>; 3],
}
impl Triangle {
pub fn new(v0: Vector3<f32>, v1: Vector3<f32>, v2: Vector3<f32>) -> Triangle {
Triangle {
points: [v0, v1, v2],
}
}
}
struct View {
position: Vector3<f32>,
look_at: Vector3<f32>,
fov: f32,
}
fn main() {
let input_data = [Triangle::new(
vec3(0.0, 0.0, 0.0),
vec3(1.0, 0.0, 0.0),
vec3(1.0, 1.0, 0.0),
)];
let view = View {
position: vec3(0.0, -4.0, 4.0),
look_at: vec3(0.0, 0.0, 0.0),
fov: 45.0,
};
let (origin, direction) = calculate_ray(640, 360, 1280, 720, &view);
let color = pixel_color(origin, direction, &input_data);
//debug_raytracer(1280, 720, &view, &input_data);
}
fn f_to_u(color: f32) -> u8 {
((color * 255.0) / 1.0) as u8
}
fn calculate_ray(
x: u32,
y: u32,
dim_x: u32,
dim_y: u32,
view: &View,
) -> (Vector3<f32>, Vector3<f32>) {
let aspect_ratio = dim_x as f32 / dim_y as f32;
let p_x = (2.0 * ((x as f32 + 0.5) / dim_x as f32) - 1.0)
* (view.fov / 2.0 * M_PI / 180.0).tan()
* aspect_ratio;
let p_y = (1.0 - 2.0 * (((dim_y - y) as f32 + 0.5) / dim_y as f32))
* (view.fov / 2.0 * M_PI / 180.0).tan();
let camera_to_world = Matrix4::look_at(
Point3::from_vec(view.position),
Point3::from_vec(view.look_at),
vec3(0.0, 0.0, 1.0),
)
.inverse_transform()
.unwrap();
let origin = camera_to_world * vec4(0.0, 0.0, 0.0, 1.0);
let direction = camera_to_world * vec4(p_x, p_y, -1.0, 1.0);
(origin.truncate(), direction.truncate().normalize())
}
fn debug_raytracer(dim_x: u32, dim_y: u32, view: &View, data: &[Triangle]) {
let mut imgbuf = ImageBuffer::new(dim_x, dim_y);
for (x, y, pixel) in imgbuf.enumerate_pixels_mut() {
let (origin, direction) = calculate_ray(x, y, dim_x, dim_y, view);
let color = pixel_color(origin, direction, data);
*pixel = image::Rgb([f_to_u(color.x), f_to_u(color.y), f_to_u(color.z)]);
}
imgbuf.save("raytrace.png").unwrap();
}
fn pixel_color(orig: Vector3<f32>, dir: Vector3<f32>, data: &[Triangle]) -> Vector3<f32> {
let mut final_color = vec3(0.0, 1.0, 0.0);
let mut closest_value = MAX;
let mut closest_index = -1;
for i in 0..data.len() {
let v0 = data[i].points[0];
let v1 = data[i].points[1];
let v2 = data[i].points[2];
let mut t = 0.0;
if ray_triangle_intersect_naive(orig, dir, v0, v1, v2, &mut t) {
if t < closest_value {
closest_index = i as i32;
closest_value = t;
}
}
}
if closest_index != -1 {
final_color = vec3(1.0, 0.0, 0.0);
}
return final_color;
}
// source: https://www.scratchapixel.com/lessons/3d-basic-rendering/ray-tracing-rendering-a-triangle/moller-trumbore-ray-triangle-intersection
fn ray_triangle_intersect_mt(
orig: Vector3<f32>,
dir: Vector3<f32>,
v0: Vector3<f32>,
v1: Vector3<f32>,
v2: Vector3<f32>,
t: &mut f32,
) -> bool {
let v0v1 = v1 - v0;
let v0v2 = v2 - v0;
let pvec = dir.cross(v0v2);
let det = v0v1.dot(pvec);
// ray and triangle are parallel if det is close to 0
if det.abs() < 0.001 {
return false;
}
let inv_det = 1.0 / det;
let tvec = orig - v0;
let u = tvec.dot(pvec) * inv_det;
if u < 0.0 || u > 1.0 {
return false;
}
let qvec = tvec.cross(v0v1);
let v = dir.dot(qvec) * inv_det;
if v < 0.0 || u + v > 1.0 {
return false;
}
*t = v0v2.dot(qvec) * inv_det;
true
}
fn ray_triangle_intersect_naive(
orig: Vector3<f32>,
dir: Vector3<f32>,
v0: Vector3<f32>,
v1: Vector3<f32>,
v2: Vector3<f32>,
t: &mut f32,
) -> bool {
// compute plane's normal
let v0v1 = v1 - v0;
let v0v2 = v2 - v0;
// no need to normalize
let N = v0v1.cross(v0v2);
let area2 = N.magnitude();
// Step 1: finding P
// check if ray and plane are parallel ?
let NdotRayDirection = N.dot(dir);
if NdotRayDirection.abs() < 0.001
// almost 0
{
return false;
} // they are parallel so they don't intersect !
// compute d parameter using equation 2
let d = N.dot(v0);
// compute t (equation 3)
*t = (N.dot(orig) + d) / NdotRayDirection;
// check if the triangle is in behind the ray
if *t < 0.0 {
return false;
} // the triangle is behind
// compute the intersection point using equation 1
let P = orig + *t * dir;
// Step 2: inside-outside test
let mut C; // vector perpendicular to triangle's plane
// edge 0
let edge0 = v1 - v0;
let vp0 = P - v0;
C = edge0.cross(vp0);
if N.dot(C) < 0.0 {
return false;
} // P is on the right side
// edge 1
let edge1 = v2 - v1;
let vp1 = P - v1;
C = edge1.cross(vp1);
if N.dot(C) < 0.0 {
return false;
} // P is on the right side
// edge 2
let edge2 = v0 - v2;
let vp2 = P - v2;
C = edge2.cross(vp2);
if N.dot(C) < 0.0 {
return false;
} // P is on the right side;
true // this ray hits the triangle
}