ft: tri support

This commit is contained in:
2026-04-30 16:28:16 +02:00
parent 388fbcbb8a
commit 27bdce5882
7 changed files with 204 additions and 12 deletions

38
scenes/withTriangle.json Normal file
View File

@@ -0,0 +1,38 @@
{
"camera": {
"image_width": 400,
"image_height": 300,
"anti_alias_rate": 23,
"max_depth": 10,
"fov": 20.0,
"look_from": { "x": 20, "y": 25, "z": 40 },
"look_at": { "x": 0.0, "y": 0.0, "z": -1.0 },
"vup": { "x": 0.0, "y": 1.0, "z": 0.0 },
"defocus_blur": false,
"defocus_angle": 0,
"focus_dist": 15.68
},
"materials": [
{ "type": "metal", "albedo": { "x": 0.9, "y": 0.4, "z": 0.2 }, "prob": 1.0, "fuzz": 0.1 },
{ "type": "metal", "albedo": { "x": 0.7, "y": 0.4, "z": 0.2 }, "prob": 1.0, "fuzz": 0.1 },
{ "type": "lambertian", "albedo": { "x": 0.8, "y": 0.8, "z": 0.0 }, "prob": 1.0 },
{ "type": "lambertian", "albedo": { "x": 0.1, "y": 0.2, "z": 0.5 }, "prob": 1.0 },
{ "type": "dielectric", "refraction_index": 1.5},
{ "type": "dielectric", "refraction_index": 0.67},
{ "type": "metal", "albedo": { "x": 0.8, "y": 0.6, "z": 0.2 }, "prob": 1.0, "fuzz": 1.0 }
],
"objects": [
{ "type": "sphere", "center": { "x": 0, "y": 0.7, "z": -0.4 }, "radius": 0.2, "material": 0},
{ "type": "sphere", "center": { "x": 0.0, "y": 0.5, "z": -0.8 }, "radius": 0.1, "material": 1},
{ "type": "sphere", "center": { "x": 0.0, "y": -100.5, "z": -1.0 }, "radius": 100.0, "material": 2},
{ "type": "sphere", "center": { "x": 0.0, "y": 0.0, "z": -1.2 }, "radius": 0.5, "material": 3},
{ "type": "sphere", "center": { "x": -1, "y": 0, "z": -1 }, "radius": 0.4, "material": 5},
{ "type": "sphere", "center": { "x": 1, "y": 0, "z": -1 }, "radius": 0.5, "material": 6},
{ "type": "sphere", "center": { "x": 20, "y": 7, "z": -15 }, "radius": 10.5, "material": 0},
{ "type": "triangle", "p1": { "x": 0, "y": 0, "z": -4}, "p2": { "x": 0, "y": 0, "z": 4 }, "p3": { "x": 0, "y": 7, "z": 0 }, "material": 0}
]
}

View File

@@ -112,7 +112,7 @@ impl Camera {
self.v = self.w.cross(&self.u);
// viewport
let viewport_height = 2. * h * self.focus_dist;
let viewport_height = 2. * h * self.focus_dist;
let viewport_width = viewport_height * (self.image_width as f32 / self.image_height as f32);
let viewport_u = viewport_width * self.u;
let viewport_v = viewport_height * -self.v;
@@ -125,7 +125,7 @@ impl Camera {
self.dirty = false;
if self.defocus_angle != 0. {
let defocus_radius = self.focus_dist * deg_to_rad(self.defocus_angle/2.).tan();
let defocus_radius = self.focus_dist * deg_to_rad(self.defocus_angle / 2.).tan();
self.defocus_disk_u = self.u * defocus_radius;
self.defocus_disk_v = self.v * defocus_radius;
}
@@ -180,7 +180,7 @@ impl Camera {
self.dirty = true;
}
}
fn defocus_disk_sample(&self) -> Vec3 {
let p = Vec3::random_in_unit_disk();
self.look_from + (p.x() * self.defocus_disk_u) + (p.y() * self.defocus_disk_v)
@@ -206,7 +206,11 @@ impl Camera {
let pixel_loc = pixel_tl
+ (x * self.pixel_delta_u / (self.anti_alias_rate + 1) as f32)
+ (y * self.pixel_delta_v / (self.anti_alias_rate + 1) as f32);
let ray_orig = if self.defocus_angle > 0. { self.defocus_disk_sample() } else {self.look_from};
let ray_orig = if self.defocus_angle > 0. {
self.defocus_disk_sample()
} else {
self.look_from
};
let ray_dir = pixel_loc - ray_orig;
let r = Ray::new(ray_orig, ray_dir);
pixel_colour += self.ray_colour(hittables, &r, self.max_depth);
@@ -220,7 +224,7 @@ impl Camera {
}
info!("Writing image file...");
imgbuf.save("output2.png").unwrap();
imgbuf.save("output.png").unwrap();
}
fn ray_colour(&self, hittables: &Vec<Arc<dyn Hittable>>, r: &Ray, depth: u32) -> Colour {

View File

@@ -18,6 +18,7 @@ use crate::ray::Ray;
use crate::scenes::scene::Scene;
use crate::vec3::Vec3;
use dotenv::dotenv;
use log::warn;
use rand::RngExt;
// TODO: implement scene serialization
@@ -26,7 +27,8 @@ fn main() {
pretty_env_logger::init();
// TODO: use cli arg for scenefile
let json_file = "./scenes/scene.json";
let json_file = "./scenes/withTriangle.json";
// let json_file = "./scenes/scene.json";
let json_str = fs::read_to_string(json_file).expect("Reading specified scene file failed!");
let mut scene: Scene = serde_json::from_str(&json_str).unwrap();
scene.render();

View File

@@ -1,4 +1,5 @@
pub mod hit;
pub mod materials;
pub mod sphere;
pub mod triangle;
pub mod traits;

79
src/objects/triangle.rs Normal file
View File

@@ -0,0 +1,79 @@
use crate::objects::hit::Hit;
use crate::objects::traits::Hittable;
use crate::ray::Ray;
use crate::{objects::materials::traits::Material, vec3::Vec3};
use is_close::default;
use std::fmt::Debug;
use std::sync::Arc;
pub struct Triangle {
p1: Vec3,
p2: Vec3,
p3: Vec3,
material: Arc<dyn Material>,
normal: Vec3,
}
impl Triangle {
pub fn new(p1: Vec3, p2: Vec3, p3: Vec3, material: Arc<dyn Material>) -> Self {
Self {
p1,
p2,
p3,
material,
normal: (p2 - p1).cross(&(p3 - p1)),
}
}
}
impl Debug for Triangle {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Triangle")
.field("p1", &self.p1)
.field("p2", &self.p2)
.field("p3", &self.p3)
.field("material", &self.material)
.finish()
}
}
impl Hittable for Triangle {
fn hit(&self, r: &Ray) -> Option<super::hit::Hit> {
// check if ray parallel to plane
let dot = self.normal.dot(r.dir());
if default().is_close(dot, 0.) {
return None;
}
let d = (-self.normal).dot(&self.p1);
// hitpoint on plane
let t = -(self.normal.dot(&(r.origin() + d))) / dot;
// hits behind camera
if t < 0. {
return None;
};
let p = *r.origin() + t * r.dir();
let a4 = (self.p3 - self.p1).cross(&(self.p2 - self.p1)).length();
let a3 = (self.p3 - p).cross(&(self.p2 - p)).length();
let a2 = (self.p3 - p).cross(&(self.p1 - p)).length();
let a1 = (self.p2 - p).cross(&(self.p1 - p)).length();
let mut normal_copy = self.normal.clone();
let diff = (a4 - a1 - a2 - a3).abs();
if diff < 0.01 {
if self.normal.dot(&-r.dir()) < 0. {
normal_copy *= -1.; // TODO: vec3 * integer function
}
return Some(Hit::new(t, p, self.normal, self.material.clone(), true)); // TODO:
// front_face calculation; have to change with if check up there iggg
} else {
return None;
}
}
fn normal_at(&self, _p: &Vec3) -> Vec3 {
// FIXME: might cause ownership issues
return self.normal;
}
}

View File

@@ -1,4 +1,5 @@
use std::sync::Arc;
use std::fmt::Debug;
use log::warn;
use serde::Deserialize;
@@ -13,6 +14,7 @@ use crate::{
},
sphere::Sphere,
traits::Hittable,
triangle::Triangle,
},
scenes::raw_camera::RawCamera,
vec3::Vec3,
@@ -24,7 +26,29 @@ pub struct Scene {
pub objects: Vec<Arc<dyn Hittable>>,
}
impl Debug for Scene {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Scene").field("camera", &self.camera).field("materials", &self.materials).field("objects", &self.objects).finish()
}
}
impl Scene {
pub fn new(image_width: u32, image_height: u32) -> Self {
Self {
camera: Camera::new(image_width, image_height),
materials: vec![],
objects: vec![],
}
}
pub fn camera(&mut self) -> &mut Camera {
&mut self.camera
}
pub fn add_hittable(&mut self, hittable: Arc<dyn Hittable>) {
self.objects.push(hittable);
}
pub fn render(&mut self) {
self.camera.render(&self.objects);
}
@@ -68,10 +92,19 @@ struct RawSphere {
pub material: u32,
}
#[derive(Deserialize)]
struct RawTriangle {
pub p1: Vec3,
pub p2: Vec3,
pub p3: Vec3,
pub material: u32,
}
#[derive(Deserialize)]
#[serde(tag = "type", rename_all = "lowercase")]
enum HittableDef {
Sphere(RawSphere),
Triangle(RawTriangle),
}
impl HittableDef {
@@ -92,6 +125,21 @@ impl HittableDef {
materials.get(s.material as usize).unwrap().clone(),
)))
}
HittableDef::Triangle(t) => {
if t.material as usize >= materials.len() {
warn!(
"Triangle specified nonexistent material {}; skipping...",
t.material
);
return None;
}
Some(Arc::new(Triangle::new(
t.p1,
t.p2,
t.p3,
materials.get(t.material as usize).unwrap().clone(),
)))
}
}
}
}

View File

@@ -59,11 +59,7 @@ impl Vec3 {
pub fn random_unit_hemisphere(n: &Self) -> Self {
let v = Self::random_unit();
if n.dot(&v) > 0.0 {
v
} else {
-v
}
if n.dot(&v) > 0.0 { v } else { -v }
}
pub fn x(&self) -> &f32 {
@@ -190,6 +186,18 @@ impl Neg for Vec3 {
}
}
impl Add<&Vec3> for &Vec3 {
type Output = Vec3;
fn add(self, rhs: &Vec3) -> Vec3 {
Vec3 {
x: self.x + rhs.x,
y: self.y + rhs.y,
z: self.z + rhs.z,
}
}
}
impl Add<Vec3> for Vec3 {
type Output = Self;
@@ -202,6 +210,19 @@ impl Add<Vec3> for Vec3 {
}
}
impl Add<f32> for &Vec3 {
type Output = Vec3;
fn add(self, f: f32) -> Vec3 {
Vec3 {
x: self.x + f,
y: self.y + f,
z: self.z + f,
}
}
}
impl Add<f32> for Vec3 {
type Output = Self;
@@ -360,7 +381,6 @@ impl Mul<Vec3> for &f32 {
z: rhs.z * self,
}
}
}
impl MulAssign<Vec3> for Vec3 {