summaryrefslogtreecommitdiff
path: root/racer-tracer/src
diff options
context:
space:
mode:
authorSakarias Johansson <sakarias.johansson@goodbyekansas.com>2023-01-08 17:51:44 +0100
committerSakarias Johansson <sakarias.johansson@goodbyekansas.com>2023-01-08 17:51:44 +0100
commit899f81eed6c221dce22333ad03704b12d7634a54 (patch)
treee9ea6b377bada412629341e666ae5d2eb929420a /racer-tracer/src
parent928b4191bf5a0d27da6d680ccaade7f94860359e (diff)
downloadracer-tracer-899f81eed6c221dce22333ad03704b12d7634a54.tar.gz
racer-tracer-899f81eed6c221dce22333ad03704b12d7634a54.tar.xz
racer-tracer-899f81eed6c221dce22333ad03704b12d7634a54.zip
🌍 Add Geometry
- Created a trait for all geometry that has to implement a hit function. Depending on if the ray hits or not it returns an option with the color. - Add support for multiple samples per pixel Current issues: - Using cooperative multitasking which isn't that helpful in this situation since it's like running without async but without overhead. Should switch to rayon. - All data gets copied once per job. Will decide later what to do (copy or put locks and share data between jobs).
Diffstat (limited to 'racer-tracer/src')
-rw-r--r--racer-tracer/src/camera.rs13
-rw-r--r--racer-tracer/src/geometry.rs54
-rw-r--r--racer-tracer/src/geometry/sphere.rs68
-rw-r--r--racer-tracer/src/image.rs4
-rw-r--r--racer-tracer/src/main.rs76
-rw-r--r--racer-tracer/src/ray.rs2
-rw-r--r--racer-tracer/src/scene.rs58
-rw-r--r--racer-tracer/src/util.rs57
-rw-r--r--racer-tracer/src/vec3.rs104
9 files changed, 365 insertions, 71 deletions
diff --git a/racer-tracer/src/camera.rs b/racer-tracer/src/camera.rs
index 5f0abbb..2c7be12 100644
--- a/racer-tracer/src/camera.rs
+++ b/racer-tracer/src/camera.rs
@@ -1,4 +1,5 @@
use crate::image::Image;
+use crate::ray::Ray;
use crate::vec3::Vec3;
#[derive(Clone)]
@@ -9,7 +10,7 @@ pub struct Camera {
pub origin: Vec3,
pub horizontal: Vec3,
pub vertical: Vec3,
- pub lower_left_corner: Vec3,
+ pub upper_left_corner: Vec3,
}
impl Camera {
@@ -25,10 +26,16 @@ impl Camera {
origin,
horizontal,
vertical,
- lower_left_corner: origin
+ upper_left_corner: origin + vertical / 2.0
- horizontal / 2.0
- - vertical / 2.0
- Vec3::new(0.0, 0.0, focal_length),
}
}
+
+ pub fn get_ray(&self, u: f64, v: f64) -> Ray {
+ Ray::new(
+ self.origin,
+ self.upper_left_corner + u * self.horizontal - v * self.vertical - self.origin,
+ )
+ }
}
diff --git a/racer-tracer/src/geometry.rs b/racer-tracer/src/geometry.rs
new file mode 100644
index 0000000..aa1c18c
--- /dev/null
+++ b/racer-tracer/src/geometry.rs
@@ -0,0 +1,54 @@
+pub mod sphere;
+
+use crate::ray::Ray;
+use crate::vec3::Vec3;
+
+pub struct HitRecord {
+ pub point: Vec3,
+ pub normal: Vec3,
+ pub t: f64,
+ pub front_face: bool,
+ pub color: Vec3,
+}
+
+impl HitRecord {
+ fn new(point: Vec3, t: f64, color: Vec3) -> Self {
+ Self {
+ point,
+ normal: Vec3::default(),
+ t,
+ front_face: true,
+ color,
+ }
+ }
+
+ fn set_face_normal(&mut self, ray: &Ray, outward_normal: Vec3) {
+ self.front_face = ray.direction().dot(&outward_normal) < 0.0;
+ self.normal = if self.front_face {
+ outward_normal
+ } else {
+ -outward_normal
+ };
+ }
+}
+
+impl Default for HitRecord {
+ fn default() -> Self {
+ HitRecord {
+ point: Vec3::default(),
+ normal: Vec3::default(),
+ t: 0.0,
+ front_face: true,
+ color: Vec3::default(),
+ }
+ }
+}
+
+pub trait Hittable {
+ //pub trait Hittable {
+ fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord>;
+
+ // Manual ugly clone since forcing on Clone would make it a super trait.
+ // Clone requires Sized and super traits cannot be Sized.
+ fn clone_box(&self) -> Box<dyn Hittable>;
+}
diff --git a/racer-tracer/src/geometry/sphere.rs b/racer-tracer/src/geometry/sphere.rs
new file mode 100644
index 0000000..6a0ab9c
--- /dev/null
+++ b/racer-tracer/src/geometry/sphere.rs
@@ -0,0 +1,68 @@
+use std::option::Option;
+
+use crate::geometry::{HitRecord, Hittable};
+use crate::ray::Ray;
+use crate::vec3::Vec3;
+
+pub struct Sphere {
+ pos: Vec3,
+ radius: f64,
+ material: Vec3, // Just a color for now.
+}
+
+impl Sphere {
+ pub fn new(pos: Vec3, radius: f64, material: Vec3) -> Self {
+ Self {
+ pos,
+ radius,
+ material,
+ }
+ }
+}
+
+impl Clone for Sphere {
+ fn clone(&self) -> Self {
+ Self {
+ pos: self.pos,
+ radius: self.radius,
+ material: self.material,
+ }
+ }
+}
+
+impl Hittable for Sphere {
+ fn hit(&self, ray: &Ray, t_min: f64, t_max: f64) -> Option<HitRecord> {
+ let oc = ray.origin() - self.pos;
+ let a = ray.direction().length_squared();
+ let half_b = oc.dot(ray.direction());
+ let c = oc.length_squared() - self.radius * self.radius;
+ let discriminant = half_b * half_b - a * c;
+
+ if discriminant < 0.0 {
+ return None;
+ }
+
+ let sqrtd = discriminant.sqrt();
+
+ // Find the nearest root that lies in acceptable range.
+ 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;
+ }
+ }
+
+ let point = ray.at(root);
+ let outward_normal = (point - self.pos) / self.radius;
+
+ let mut hit_record = HitRecord::new(point, root, self.material);
+ hit_record.set_face_normal(ray, outward_normal);
+ Some(hit_record)
+ }
+
+ fn clone_box(&self) -> Box<dyn Hittable> {
+ Box::new(self.clone())
+ }
+}
diff --git a/racer-tracer/src/image.rs b/racer-tracer/src/image.rs
index 1b5f61e..2df11dc 100644
--- a/racer-tracer/src/image.rs
+++ b/racer-tracer/src/image.rs
@@ -3,14 +3,16 @@ pub struct Image {
pub aspect_ratio: f64,
pub width: usize,
pub height: usize,
+ pub samples_per_pixel: usize,
}
impl Image {
- pub fn new(aspect_ratio: f64, width: usize) -> Image {
+ pub fn new(aspect_ratio: f64, width: usize, samples_per_pixel: usize) -> Image {
Image {
aspect_ratio,
width,
height: (width as f64 / aspect_ratio) as usize,
+ samples_per_pixel,
}
}
}
diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs
index 1951da5..9d45762 100644
--- a/racer-tracer/src/main.rs
+++ b/racer-tracer/src/main.rs
@@ -1,51 +1,86 @@
#[macro_use]
mod error;
mod camera;
+mod geometry;
mod image;
mod ray;
+mod scene;
mod util;
mod vec3;
-use crate::vec3::Vec3;
use std::vec::Vec;
use futures::{select, stream::FuturesUnordered, stream::StreamExt};
+use geometry::Hittable;
use minifb::{Key, Window, WindowOptions};
-fn ray_color(ray: &ray::Ray) -> Vec3 {
- let unit_direction = vec3::unit_vector(ray.direction());
+use crate::camera::Camera;
+use crate::geometry::sphere::Sphere;
+use crate::image::Image;
+use crate::ray::Ray;
+use crate::scene::Scene;
+use crate::util::random_double;
+use crate::vec3::Vec3;
+
+fn ray_color(scene: &Scene, ray: &Ray) -> Vec3 {
+ if let Some(hit_record) = scene.hit(ray, 0.0, std::f64::INFINITY) {
+ //return hit_record.color;
+ return 0.5 * (hit_record.normal + Vec3::new(1.0, 1.0, 1.0));
+ }
+
+ // Sky
+ let unit_direction = ray.direction().unit_vector();
let t = 0.5 * (unit_direction.y() + 1.0);
(1.0 - t) * Vec3::new(1.0, 1.0, 1.0) + t * Vec3::new(0.5, 0.7, 1.0)
}
+// TODO: Rustify
async fn raytrace(
- camera: camera::Camera,
- image: image::Image,
+ scene: Scene,
+ camera: Camera,
+ image: Image,
row: usize,
) -> Result<(usize, Vec<u32>), error::TracerError> {
let mut buffer: Vec<u32> = vec![0; image.width as usize];
+ let mut colors: Vec<Vec3> = vec![Vec3::default(); image.width as usize];
for i in 0..buffer.len() {
- let u: f64 = i as f64 / (image.width - 1) as f64;
- let v: f64 = row as f64 / (image.height - 1) as f64;
- let ray = ray::Ray::new(
- camera.origin,
- camera.lower_left_corner + u * camera.horizontal + v * camera.vertical - camera.origin,
- );
- let col = ray_color(&ray);
- buffer[i] = col.as_color();
+ for _ in 0..image.samples_per_pixel {
+ let u: f64 = (i as f64 + random_double()) / (image.width - 1) as f64;
+ let v: f64 = (row as f64 + random_double()) / (image.height - 1) as f64;
+ colors[i] += ray_color(&scene, &camera.get_ray(u, v));
+ }
+ }
+
+ for i in 0..image.width {
+ buffer[i] = (colors[i] / image.samples_per_pixel as f64).as_color();
}
Ok((row, buffer))
}
+fn create_scene() -> Scene {
+ let mut scene = Scene::new();
+ let sphere1 = Sphere::new(Vec3::new(0.0, 0.0, -1.0), 0.5, Vec3::new(0.0, 1.0, 0.0));
+ let sphere2 = Sphere::new(
+ Vec3::new(0.0, -100.5, -1.0),
+ 100.0,
+ Vec3::new(0.0, 1.0, 0.0),
+ );
+ scene.add(Box::new(sphere1));
+ scene.add(Box::new(sphere2));
+ scene
+}
+
async fn run(
rows_per_update: u32,
aspect_ratio: f64,
- screen_height: usize,
+ screen_width: usize,
+ samples: usize,
) -> Result<(), error::TracerError> {
- let image = image::Image::new(aspect_ratio, screen_height);
+ let image = image::Image::new(aspect_ratio, screen_width, samples);
let camera = camera::Camera::new(&image, 2.0, 1.0);
+ let scene = create_scene();
let mut screen_buffer: Vec<u32> = vec![0; image.width * image.height];
let mut window = Window::new(
@@ -61,13 +96,18 @@ async fn run(
// One future per row is a bit high.
// Could do something less spammy.
for h in 0..image.height {
- futs.push(raytrace(camera.clone(), image.clone(), h));
+ // TODO: Either clone all or lock em all.
+ futs.push(raytrace(scene.clone(), camera.clone(), image.clone(), h));
}
+ // TODO: use rayon
+ // Since it's cooperative multitasking this is not really helpful at the moment.
+ // You will get pretty much get the same result without the tokio asyncness.
+ // using rayon with threads is a different matter.
let mut complete = false;
while window.is_open() && !window.is_key_down(Key::Escape) {
if !complete {
- for _ in 1..rows_per_update {
+ for _ in 0..rows_per_update {
select! {
res = futs.select_next_some() => {
let row_buffer = res.expect("Expected to get data");
@@ -95,7 +135,7 @@ async fn run(
#[tokio::main]
async fn main() {
- if let Err(e) = run(50, 16.0 / 9.0, 1200).await {
+ if let Err(e) = run(100, 16.0 / 9.0, 1200, 100).await {
eprintln!("{}", e);
std::process::exit(e.into())
}
diff --git a/racer-tracer/src/ray.rs b/racer-tracer/src/ray.rs
index 1325cc2..33e62f2 100644
--- a/racer-tracer/src/ray.rs
+++ b/racer-tracer/src/ray.rs
@@ -19,6 +19,6 @@ impl Ray {
}
pub fn at(&self, go_length: f64) -> Vec3 {
- self.origin.clone() + go_length * self.direction.clone()
+ self.origin + go_length * self.direction
}
}
diff --git a/racer-tracer/src/scene.rs b/racer-tracer/src/scene.rs
new file mode 100644
index 0000000..2d530af
--- /dev/null
+++ b/racer-tracer/src/scene.rs
@@ -0,0 +1,58 @@
+use crate::geometry::Hittable;
+
+pub struct Scene {
+ objects: Vec<Box<dyn Hittable>>,
+}
+
+impl Scene {
+ pub fn new() -> Self {
+ Self {
+ objects: Vec::new(),
+ }
+ }
+
+ pub fn add(&mut self, hittable: Box<dyn Hittable>) {
+ self.objects.push(hittable);
+ }
+}
+
+// TODO: What to do?
+// Cloning everything is nice since then every task can do whatever they like.
+// Cloning everything is bad becuse you copy everything which takes time.
+// Could also put locks on the Scene but then it becomes this global object that everyone
+// wants to access at the same time.
+// Will do some laborations later and decide on a solution.
+impl Clone for Scene {
+ fn clone(&self) -> Self {
+ let mut objects = Vec::with_capacity(self.objects.capacity());
+ for i in self.objects.iter() {
+ objects.push(i.clone_box());
+ }
+ Self { objects }
+ }
+}
+
+impl Hittable for Scene {
+ fn hit(
+ &self,
+ ray: &crate::ray::Ray,
+ t_min: f64,
+ t_max: f64,
+ ) -> Option<crate::geometry::HitRecord> {
+ let mut rec = None;
+ let mut closes_so_far = t_max;
+
+ for obj in self.objects.iter() {
+ if let Some(hit_rec) = obj.hit(ray, t_min, closes_so_far) {
+ closes_so_far = hit_rec.t;
+ rec = Some(hit_rec);
+ }
+ }
+
+ rec
+ }
+
+ fn clone_box(&self) -> Box<dyn Hittable> {
+ Box::new(self.clone())
+ }
+}
diff --git a/racer-tracer/src/util.rs b/racer-tracer/src/util.rs
index e223c67..196b82d 100644
--- a/racer-tracer/src/util.rs
+++ b/racer-tracer/src/util.rs
@@ -1,43 +1,18 @@
-pub fn hsv_to_rgb(h: f64, s: f64, v: f64) -> u32 {
- let s: f64 = s / 100.0;
- let v: f64 = v / 100.0;
- let c: f64 = s * v;
- let mut a: f64 = h / 60.0;
- a %= 2.0f64;
- let x: f64 = c * (1f64 - (a - 1f64).abs());
- let m: f64 = v - c;
+use rand::Rng;
+/*
+// For later use
+fn degrees_to_radians(degrees: f64) -> f64 {
+ degrees * std::f64::consts::PI / 180.0
+}*/
- let r: f64;
- let g: f64;
- let b: f64;
- if (0.0..60.0).contains(&h) {
- r = c;
- g = x;
- b = 0.0;
- } else if (60.0..120.0).contains(&h) {
- r = x;
- g = c;
- b = 0.0;
- } else if (120.0..180.0).contains(&h) {
- r = 0.0;
- g = c;
- b = x;
- } else if (180.0..240.0).contains(&h) {
- r = 0.0;
- g = x;
- b = c;
- } else if (240.0..300.0).contains(&h) {
- r = x;
- g = 0.0;
- b = c;
- } else {
- r = c;
- g = 0.0;
- b = x;
- }
-
- let red: u32 = ((r + m) * 255.0) as u32;
- let green: u32 = ((g + m) * 255.0) as u32;
- let blue: u32 = ((b + m) * 255.0) as u32;
- ((red as u32) << 16) | ((green as u32) << 8) | blue as u32
+pub fn random_double() -> f64 {
+ let mut rng = rand::thread_rng();
+ rng.gen::<f64>()
}
+
+/*
+// For later use
+pub fn random_double_range(min: f64, max: f64) -> f64 {
+ let mut rng = rand::thread_rng();
+ rng.gen_range(min..max)
+}*/
diff --git a/racer-tracer/src/vec3.rs b/racer-tracer/src/vec3.rs
index 47df292..10f3bfa 100644
--- a/racer-tracer/src/vec3.rs
+++ b/racer-tracer/src/vec3.rs
@@ -86,15 +86,31 @@ impl ops::AddAssign<Vec3> for Vec3 {
}
}
+fn vec_sub(v1: &[f64; 3], v2: &[f64; 3]) -> Vec3 {
+ Vec3::new(v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2])
+}
+
impl ops::Sub<Vec3> for Vec3 {
type Output = Vec3;
fn sub(self, rhs: Vec3) -> Self::Output {
- Vec3::new(
- rhs.data[0] - self.data[0],
- rhs.data[1] - self.data[1],
- rhs.data[2] - self.data[2],
- )
+ vec_sub(&self.data, &rhs.data)
+ }
+}
+
+impl ops::Sub<&Vec3> for Vec3 {
+ type Output = Vec3;
+
+ fn sub(self, rhs: &Vec3) -> Self::Output {
+ vec_sub(&self.data, &rhs.data)
+ }
+}
+
+impl ops::Sub<Vec3> for &Vec3 {
+ type Output = Vec3;
+
+ fn sub(self, rhs: Vec3) -> Self::Output {
+ vec_sub(&self.data, &rhs.data)
}
}
@@ -151,7 +167,7 @@ impl ops::Mul<&Vec3> for f64 {
type Output = Vec3;
fn mul(self, rhs: &Vec3) -> Self::Output {
- Vec3::new(rhs.data[0] * self, rhs.data[1] * self, rhs.data[2] * self)
+ *rhs * self
}
}
@@ -171,7 +187,7 @@ impl ops::Mul<Vec3> for f64 {
type Output = Vec3;
fn mul(self, rhs: Vec3) -> Self::Output {
- rhs * self
+ Vec3::new(rhs.data[0] * self, rhs.data[1] * self, rhs.data[2] * self)
}
}
@@ -191,6 +207,14 @@ impl ops::Neg for Vec3 {
}
}
+impl PartialEq for Vec3 {
+ fn eq(&self, other: &Self) -> bool {
+ self.data[0] == other.data[0]
+ && self.data[1] == other.data[1]
+ && self.data[2] == other.data[2]
+ }
+}
+
impl fmt::Display for Vec3 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(
@@ -202,3 +226,69 @@ impl fmt::Display for Vec3 {
)
}
}
+
+impl std::fmt::Debug for Vec3 {
+ fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
+ f.debug_struct("Vec3").field("data", &self.data).finish()
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use super::*;
+
+ #[test]
+ fn add() {
+ let v1 = Vec3::new(1.0, 2.0, 3.0);
+ let v2 = Vec3::new(2.0, 4.0, 6.0);
+ let v3 = v1 + v2;
+ let v4 = v2 + v1;
+
+ assert_eq!(*v3.x(), 3.0);
+ assert_eq!(*v3.y(), 6.0);
+ assert_eq!(*v3.z(), 9.0);
+ assert_eq!(v3, v4);
+ }
+
+ #[test]
+ fn sub() {
+ let v1 = Vec3::new(1.0, 2.0, 3.0);
+ let v2 = Vec3::new(2.0, 4.0, 6.0);
+ let v3 = v1 - v2;
+ assert_eq!(v3.x(), &-1.0);
+ assert_eq!(v3.y(), &-2.0);
+ assert_eq!(v3.z(), &-3.0);
+
+ let v4 = v2 - v1;
+ assert_eq!(v4.x(), &1.0);
+ assert_eq!(v4.y(), &2.0);
+ assert_eq!(v4.z(), &3.0);
+ }
+
+ #[test]
+ fn mul() {
+ let v1 = Vec3::new(1.0, -2.0, 3.0);
+ let v2 = v1 * 5.0;
+
+ assert_eq!(v2.x(), &5.0);
+ assert_eq!(v2.y(), &-10.0);
+ assert_eq!(v2.z(), &15.0);
+
+ let v3 = Vec3::new(4.0, 8.0, 16.0);
+ let v4 = v1 * v3;
+
+ assert_eq!(v4.x(), &4.0);
+ assert_eq!(v4.y(), &-16.0);
+ assert_eq!(v4.z(), &48.0);
+ }
+
+ #[test]
+ fn div() {
+ let v1 = Vec3::new(1.0, -2.0, 3.0);
+ let v2 = v1 / 2.0;
+
+ assert_eq!(v2.x(), &0.5);
+ assert_eq!(v2.y(), &-1.0);
+ assert_eq!(v2.z(), &1.5);
+ }
+}