From 6a3348d727e01812e63d66feff6d170d23a11176 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20R=C3=B6ger?= Date: Thu, 28 Nov 2024 01:57:40 +0100 Subject: [PATCH] feat(raytracer): add native rt functions to lisp --- Cargo.toml | 10 +- flake.nix | 10 +- src/bin/rt_demo.rs | 19 ++-- src/bin/rt_lisp_demo.rs | 46 ++++++++ src/raytracer/camera.rs | 16 +++ src/raytracer/lisp.rs | 226 ++++++++++++++++++++++++++++++++++++++++ src/raytracer/mod.rs | 1 + src/raytracer/plane.rs | 52 +++++++-- src/raytracer/scene.rs | 29 +++++- src/raytracer/sphere.rs | 21 +++- src/raytracer/types.rs | 156 ++++++++++++++++++++++++++- 11 files changed, 554 insertions(+), 32 deletions(-) create mode 100644 src/bin/rt_lisp_demo.rs create mode 100644 src/raytracer/lisp.rs diff --git a/Cargo.toml b/Cargo.toml index 564f5e5..a8c23c1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "lispers" -description = "lisp interpreter in rust" +description = "lisp interpreter in rust for raytracing" publish = false version = "0.1.0" @@ -11,13 +11,17 @@ name = "lispers" path = "src/lib.rs" [[bin]] -name = "demo" -path = "src/bin/demo.rs" +name = "lisp_demo" +path = "src/bin/lisp_demo.rs" [[bin]] name = "repl" path = "src/bin/repl.rs" +[[bin]] +name = "rt_lisp_demo" +path = "src/bin/rt_lisp_demo.rs" + [dependencies] as-any = "0.3.1" futures = "0.3.30" diff --git a/flake.nix b/flake.nix index 1857037..868012b 100644 --- a/flake.nix +++ b/flake.nix @@ -63,9 +63,9 @@ }; in rec { apps = rec { - demo = { + lisp_demo = { type = "app"; - program = "${packages.default}/bin/demo"; + program = "${packages.default}/bin/lisp_demo"; }; repl = { type = "app"; @@ -75,7 +75,11 @@ type = "app"; program = "${packages.default}/bin/rt_demo"; }; - default = demo; + rt_demo_lisp = { + type = "app"; + program = "${packages.default}/bin/rt_demo_lisp"; + }; + default = rt_demo_lisp; }; packages = rec { lispers = cargoNix.rootCrate.build; diff --git a/src/bin/rt_demo.rs b/src/bin/rt_demo.rs index 7ef5fe6..62c8aa0 100644 --- a/src/bin/rt_demo.rs +++ b/src/bin/rt_demo.rs @@ -3,10 +3,9 @@ use lispers::raytracer::{ plane::Checkerboard, scene::Scene, sphere::Sphere, - types::{Color, Light, Material, Point3, Vector3}, + types::{Color, Light, Material, Point3, RTObjectWrapper, Vector3}, }; extern crate nalgebra as na; -use std::sync::Arc; use std::time::Instant; fn main() { @@ -23,7 +22,7 @@ fn main() { color: Color::new(1.0, 1.0, 1.0), }); - scene.add_object(Arc::new(Checkerboard::new( + scene.add_object(RTObjectWrapper::new(Box::new(Checkerboard::new( Point3::new(0.0, -1.0, 0.0), Vector3::new(0.0, 1.0, 0.0), Material::new( @@ -42,9 +41,9 @@ fn main() { ), 0.3, Vector3::new(0.0, 0.0, 1.0), - ))); + )))); - scene.add_object(Arc::new(Sphere::new( + scene.add_object(RTObjectWrapper::new(Box::new(Sphere::new( Point3::new(-2.0, 0.0, 1.0), 1.0, Material::new( @@ -54,9 +53,9 @@ fn main() { 20.0, 0.3, ), - ))); + )))); - scene.add_object(Arc::new(Sphere::new( + scene.add_object(RTObjectWrapper::new(Box::new(Sphere::new( Point3::new(0.2, -0.5, -0.2), 0.5, Material::new( @@ -66,9 +65,9 @@ fn main() { 20.0, 0.3, ), - ))); + )))); - scene.add_object(Arc::new(Sphere::new( + scene.add_object(RTObjectWrapper::new(Box::new(Sphere::new( Point3::new(-0.5, 0.5, -2.0), 1.5, Material::new( @@ -78,7 +77,7 @@ fn main() { 20.0, 0.3, ), - ))); + )))); let camera = Camera::new( Point3::new(0.0, 0.7, 5.0), diff --git a/src/bin/rt_lisp_demo.rs b/src/bin/rt_lisp_demo.rs new file mode 100644 index 0000000..9dad0f6 --- /dev/null +++ b/src/bin/rt_lisp_demo.rs @@ -0,0 +1,46 @@ +use lispers::lisp::environment::EnvironmentLayer; +use lispers::lisp::prelude::mk_prelude; +use lispers::lisp::{eval, Environment}; +use lispers::parser::ExpressionStream; +use lispers::raytracer::lisp::mk_raytrace; + +fn main() { + let programs = [ + "(set 'blue (material (color 0 0 1) (color 0 0 1) (color 0 0 0.6) 50 0.25))", + "(set 'green (material (color 0 1 0) (color 0 1 0) (color 0 0.6 0) 50 0.25))", + "(set 'white (material (color 1 1 1) (color 1 1 1) (color 0.6 0.6 0.6) 100 0.5))", + "(set 'black (material (color 0 0 0) (color 0 0 0) (color 0.6 0.6 0.6) 100 0.5))", + "(set 's1 (sphere (point 0 1 0) 1 blue))", + "(set 's2 (sphere (point 2 0.5 2) 0.5 green))", + "(set 'p1 (checkerboard (point 0 0 0) (vector 0 1 0) black white 0.5 (vector 0.5 0 1)))", + "(set 'l1 (light (point 3 10 5) (color 1 1 1)))", + "(set 'l2 (light (point 2 10 5) (color 1 1 1)))", + "(set 'scn (scene (color 0.1 0.1 0.1) '(s1 s2 p1) '(l1 l2)))", + "(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))", + "(render cam scn 5 4 \"rt-lisp-demo.png\")", + ]; + + let mut layer = EnvironmentLayer::new(); + mk_prelude(&mut layer); + mk_raytrace(&mut layer); + + let environment = Environment::from_layer(layer); + + for r in ExpressionStream::from_char_stream(programs.iter().map(|p| p.chars()).flatten()) { + match r { + Err(err) => { + println!("ParserError: {:?}", err); + break; + } + Ok(expr) => { + println!("Evaluating: {}", expr.clone()); + match eval(&environment, expr) { + Ok(e) => println!("=> {}", e), + Err(e) => println!("Error: {}", e), + } + } + } + } + + println!("Interpreter Done!"); +} diff --git a/src/raytracer/camera.rs b/src/raytracer/camera.rs index 092f33d..90a0952 100644 --- a/src/raytracer/camera.rs +++ b/src/raytracer/camera.rs @@ -1,3 +1,5 @@ +use std::fmt::Display; + use super::{ scene::Scene, types::{Color, Point3, Ray, Scalar, Vector3}, @@ -6,6 +8,7 @@ use image::RgbImage; use rayon::prelude::*; /// A camera that can render a scene. +#[derive(Clone, PartialEq, Debug)] pub struct Camera { /// Position of the camera's eye. position: Point3, @@ -106,3 +109,16 @@ impl Camera { img } } + +impl Display for Camera { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "Camera {{ position: {}, lower_left: {}, x_dir: {}, y_dir: {}, width: {}, height: {} }}", + self.position, self.lower_left, self.x_dir, self.y_dir, self.width, self.height) + } +} + +impl PartialOrd for Camera { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} diff --git a/src/raytracer/lisp.rs b/src/raytracer/lisp.rs new file mode 100644 index 0000000..77b2a27 --- /dev/null +++ b/src/raytracer/lisp.rs @@ -0,0 +1,226 @@ +use crate::{ + lisp::{ + environment::EnvironmentLayer, + eval::{eval, EvalError}, + expression::ForeignDataWrapper, + Environment, Expression, + }, + raytracer::{scene::Scene, types::Light}, +}; + +use super::{ + camera::Camera, + plane::{Checkerboard, Plane}, + sphere::Sphere, + types::{Color, Material, Point3, RTObjectWrapper, Vector3}, +}; + +pub fn point(env: &Environment, expr: Expression) -> Result { + let [x, y, z]: [Expression; 3] = expr.try_into()?; + + let x: f64 = eval(env, x)?.try_into()?; + let y: f64 = eval(env, y)?.try_into()?; + let z: f64 = eval(env, z)?.try_into()?; + + Ok(ForeignDataWrapper::new(Point3::new(x, y, z)).into()) +} + +pub fn vector(env: &Environment, expr: Expression) -> Result { + let [x, y, z]: [Expression; 3] = expr.try_into()?; + + let x: f64 = eval(env, x)?.try_into()?; + let y: f64 = eval(env, y)?.try_into()?; + let z: f64 = eval(env, z)?.try_into()?; + + Ok(ForeignDataWrapper::new(Vector3::new(x, y, z)).into()) +} + +pub fn color(env: &Environment, expr: Expression) -> Result { + let [r, g, b]: [Expression; 3] = expr.try_into()?; + + let r: f64 = eval(env, r)?.try_into()?; + let g: f64 = eval(env, g)?.try_into()?; + let b: f64 = eval(env, b)?.try_into()?; + + Ok(ForeignDataWrapper::new(Color::new(r, g, b)).into()) +} + +pub fn light(env: &Environment, expr: Expression) -> Result { + let [pos, col]: [Expression; 2] = expr.try_into()?; + + let pos: ForeignDataWrapper = eval(env, pos)?.try_into()?; + let col: ForeignDataWrapper = eval(env, col)?.try_into()?; + + let pos: Point3 = *pos; + let col: Color = *col; + + Ok(ForeignDataWrapper::new(Light::new(pos, col)).into()) +} + +pub fn material(env: &Environment, expr: Expression) -> Result { + let [amb, dif, spe, shi, mir]: [Expression; 5] = expr.try_into()?; + + let amb: ForeignDataWrapper = eval(env, amb)?.try_into()?; + let dif: ForeignDataWrapper = eval(env, dif)?.try_into()?; + let spe: ForeignDataWrapper = eval(env, spe)?.try_into()?; + let shi: f64 = eval(env, shi)?.try_into()?; + let mir: f64 = eval(env, mir)?.try_into()?; + + let amb: Color = *amb; + let dif: Color = *dif; + let spe: Color = *spe; + + Ok(ForeignDataWrapper::new(Material::new(amb, dif, spe, shi, mir)).into()) +} + +pub fn sphere(env: &Environment, expr: Expression) -> Result { + let [pos, rad, mat]: [Expression; 3] = expr.try_into()?; + + let pos: ForeignDataWrapper = eval(env, pos)?.try_into()?; + let rad: f64 = eval(env, rad)?.try_into()?; + let mat: ForeignDataWrapper = eval(env, mat)?.try_into()?; + + let pos: Point3 = *pos; + let mat: Material = *mat; + + Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(pos, rad, mat))).into()) +} + +pub fn plane(env: &Environment, expr: Expression) -> Result { + let [pos, dir, mat]: [Expression; 3] = expr.try_into()?; + + let pos: ForeignDataWrapper = eval(env, pos)?.try_into()?; + let dir: ForeignDataWrapper = eval(env, dir)?.try_into()?; + let mat: ForeignDataWrapper = eval(env, mat)?.try_into()?; + + let pos: Point3 = *pos; + let dir: Vector3 = *dir; + let mat: Material = *mat; + + Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Plane::new(pos, dir, mat))).into()) +} + +pub fn checkerboard(env: &Environment, expr: Expression) -> Result { + let [pos, norm, mat1, mat2, sca, up]: [Expression; 6] = expr.try_into()?; + + let pos: ForeignDataWrapper = eval(env, pos)?.try_into()?; + let norm: ForeignDataWrapper = eval(env, norm)?.try_into()?; + let mat1: ForeignDataWrapper = eval(env, mat1)?.try_into()?; + let mat2: ForeignDataWrapper = eval(env, mat2)?.try_into()?; + let sca: f64 = eval(env, sca)?.try_into()?; + let up: ForeignDataWrapper = eval(env, up)?.try_into()?; + + let pos: Point3 = *pos; + let norm: Vector3 = *norm; + let mat1: Material = *mat1; + let mat2: Material = *mat2; + let up: Vector3 = *up; + + Ok( + ForeignDataWrapper::new(RTObjectWrapper::from(Checkerboard::new( + pos, norm, mat1, mat2, sca, up, + ))) + .into(), + ) +} + +pub fn scene(env: &Environment, expr: Expression) -> Result { + let [amb, objs, lgts]: [Expression; 3] = expr.try_into()?; + + let amb: ForeignDataWrapper = eval(env, amb)?.try_into()?; + let objs: Vec = eval(env, objs)?.try_into()?; + let lgts: Vec = eval(env, lgts)?.try_into()?; + + let mut scene = Scene::new(); + + scene.set_ambient(*amb); + for o in objs { + let o: ForeignDataWrapper = eval(env, o)?.try_into()?; + scene.add_object(o.clone()); + } + for l in lgts { + let l: ForeignDataWrapper = eval(env, l)?.try_into()?; + scene.add_light(*l); + } + + Ok(ForeignDataWrapper::new(scene).into()) +} + +pub fn scene_add_object(env: &Environment, expr: Expression) -> Result { + let [sce, obj]: [Expression; 2] = expr.try_into()?; + + let mut sce: ForeignDataWrapper = eval(env, sce)?.try_into()?; + let obj: ForeignDataWrapper = eval(env, obj)?.try_into()?; + + sce.add_object(obj.clone()); + + Ok(sce.into()) +} + +pub fn scene_add_light(env: &Environment, expr: Expression) -> Result { + let [sce, lgt]: [Expression; 2] = expr.try_into()?; + + let mut sce: ForeignDataWrapper = eval(env, sce)?.try_into()?; + let lgt: ForeignDataWrapper = eval(env, lgt)?.try_into()?; + + sce.add_light(*lgt); + + Ok(sce.into()) +} + +pub fn camera(env: &Environment, expr: Expression) -> Result { + let [pos, cnt, up, fovy, w, h]: [Expression; 6] = expr.try_into()?; + + let pos: ForeignDataWrapper = eval(env, pos)?.try_into()?; + let cnt: ForeignDataWrapper = eval(env, cnt)?.try_into()?; + let up: ForeignDataWrapper = eval(env, up)?.try_into()?; + let fovy: f64 = eval(env, fovy)?.try_into()?; + let w: i64 = eval(env, w)?.try_into()?; + let h: i64 = eval(env, h)?.try_into()?; + + Ok(ForeignDataWrapper::new(Camera::new(*pos, *cnt, *up, fovy, w as usize, h as usize)).into()) +} + +pub fn render(env: &Environment, expr: Expression) -> Result { + let [cam, sce, dpt, sbp, out]: [Expression; 5] = expr.try_into()?; + + let cam: ForeignDataWrapper = eval(env, cam)?.try_into()?; + let sce: ForeignDataWrapper = eval(env, sce)?.try_into()?; + let dpt: i64 = eval(env, dpt)?.try_into()?; + let sbp: i64 = eval(env, sbp)?.try_into()?; + let out: String = eval(env, out)?.try_into()?; + + println!("Rendering to {}...", out); + let img = cam.render(&sce, dpt as u32, sbp as u32); + + match img.save(out) { + Ok(_) => Ok(Expression::Nil), + Err(e) => Err(EvalError::RuntimeError(e.to_string())), + } +} + +/// Adds the raytracing functions to the given environment layer. +pub fn mk_raytrace(layer: &mut EnvironmentLayer) { + layer.set("point".to_string(), Expression::Function(point)); + layer.set("vector".to_string(), Expression::Function(vector)); + layer.set("color".to_string(), Expression::Function(color)); + layer.set("light".to_string(), Expression::Function(light)); + layer.set("material".to_string(), Expression::Function(material)); + layer.set("plane".to_string(), Expression::Function(plane)); + layer.set( + "checkerboard".to_string(), + Expression::Function(checkerboard), + ); + layer.set("sphere".to_string(), Expression::Function(sphere)); + layer.set("scene".to_string(), Expression::Function(scene)); + layer.set( + "scene-add-object".to_string(), + Expression::Function(scene_add_object), + ); + layer.set( + "scene-add-light".to_string(), + Expression::Function(scene_add_light), + ); + layer.set("camera".to_string(), Expression::Function(camera)); + layer.set("render".to_string(), Expression::Function(render)); +} diff --git a/src/raytracer/mod.rs b/src/raytracer/mod.rs index d7d66f4..d4f5d00 100644 --- a/src/raytracer/mod.rs +++ b/src/raytracer/mod.rs @@ -1,4 +1,5 @@ pub mod camera; +pub mod lisp; pub mod plane; pub mod scene; pub mod sphere; diff --git a/src/raytracer/plane.rs b/src/raytracer/plane.rs index 90e05fa..984f3da 100644 --- a/src/raytracer/plane.rs +++ b/src/raytracer/plane.rs @@ -3,6 +3,7 @@ use super::types::{Intersect, Material, Point3, Scalar, Vector3}; extern crate nalgebra as na; /// An infinite plane in 3D space. +#[derive(PartialEq, Clone, Debug)] pub struct Plane { /// The position of the plane. position: Point3, @@ -13,6 +14,7 @@ pub struct Plane { } /// A infinite checkerboard plane in 3D space. +#[derive(PartialEq, Clone, Debug)] pub struct Checkerboard { /// The base plane containing the "white" material base: Plane, @@ -65,14 +67,14 @@ impl Checkerboard { } impl Intersect for Plane { - fn intersect<'a>( - &'a self, + fn intersect( + &self, ray: &super::types::Ray, ) -> Option<( Point3, Vector3, super::types::Scalar, - &'a super::types::Material, + super::types::Material, )> { let denom = self.normal.dot(&ray.direction); if denom != 0.0 { @@ -81,7 +83,7 @@ impl Intersect for Plane { if t > 1e-5 { let point = ray.origin + ray.direction * t; - return Some((point, self.normal, t, &self.material)); + return Some((point, self.normal, t, self.material.clone())); } } None @@ -89,14 +91,14 @@ impl Intersect for Plane { } impl Intersect for Checkerboard { - fn intersect<'a>( - &'a self, + fn intersect( + &self, ray: &super::types::Ray, ) -> Option<( Point3, Vector3, super::types::Scalar, - &'a super::types::Material, + super::types::Material, )> { if let Some((point, normal, t, material)) = self.base.intersect(ray) { let v3 = point - self.base.position; @@ -105,12 +107,44 @@ impl Intersect for Checkerboard { if ((v2.x / self.scale).round() % 2.0 == 0.0) == ((v2.y / self.scale).round() % 2.0 == 0.0) { - Some((point, normal, t, material)) + Some((point, normal, t, material.clone())) } else { - Some((point, normal, t, &self.material_alt)) + Some((point, normal, t, self.material_alt.clone())) } } else { None } } } + +impl std::fmt::Display for Plane { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "(plane position: {}, normal: {}, material: {})", + self.position, self.normal, self.material + ) + } +} + +impl std::fmt::Display for Checkerboard { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "(checkerboard position: {}, normal: {}, material1: {}, material2: {}, scale: {})", + self.base.position, self.base.normal, self.base.material, self.material_alt, self.scale + ) + } +} + +impl PartialOrd for Plane { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} + +impl PartialOrd for Checkerboard { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} diff --git a/src/raytracer/scene.rs b/src/raytracer/scene.rs index e9e8c84..d25a29f 100644 --- a/src/raytracer/scene.rs +++ b/src/raytracer/scene.rs @@ -1,21 +1,24 @@ +use std::fmt::Display; + use super::types::Color; use super::types::Intersect; use super::types::Light; use super::types::Material; use super::types::Point3; +use super::types::RTObjectWrapper; use super::types::Ray; use super::types::Vector3; use super::vec::mirror; use super::vec::reflect; -use std::sync::Arc; extern crate nalgebra as na; /// A scene is a collection of objects and lights, and provides a method to trace a ray through the scene. +#[derive(Debug, PartialEq, Clone)] pub struct Scene { /// The ambient light of the scene ambient: Color, /// The objects in the scene - objects: Vec>, + objects: Vec, /// The lights in the scene lights: Vec, } @@ -36,7 +39,7 @@ impl Scene { } /// Add an object to the scene - pub fn add_object(&mut self, obj: Arc) { + pub fn add_object(&mut self, obj: RTObjectWrapper) { self.objects.push(obj); } @@ -61,7 +64,7 @@ impl Scene { { Some((isect_pt, isect_norm, _, material)) => { // Lighting of material at the intersection point - let color = self.lighting(-&ray.direction, material, isect_pt, isect_norm); + let color = self.lighting(-&ray.direction, &material, isect_pt, isect_norm); // Calculate reflections, if the material has mirror properties if material.mirror > 0.0 { @@ -128,3 +131,21 @@ impl Scene { color } } + +impl PartialOrd for Scene { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} + +impl Display for Scene { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "(scene ambient: {}, #objects: {}, #lights: {})", + self.ambient, + self.objects.len(), + self.lights.len() + ) + } +} diff --git a/src/raytracer/sphere.rs b/src/raytracer/sphere.rs index 1430594..8627456 100644 --- a/src/raytracer/sphere.rs +++ b/src/raytracer/sphere.rs @@ -3,6 +3,7 @@ use super::types::{Intersect, Material, Point3, Ray, Scalar, Vector3}; extern crate nalgebra as na; /// A sphere in 3D space +#[derive(PartialEq, Clone, Debug)] pub struct Sphere { /// Center of the sphere center: Point3, @@ -27,7 +28,7 @@ impl Sphere { const EPSILON: Scalar = 1e-5; impl Intersect for Sphere { - fn intersect<'a>(&'a self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, &'a Material)> { + fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> { let co = ray.origin - self.center; let a = ray.direction.dot(&ray.direction); @@ -55,7 +56,7 @@ impl Intersect for Sphere { isect_pt, (isect_pt - self.center) / self.radius, t, - &self.material, + self.material.clone(), )); } } @@ -63,3 +64,19 @@ impl Intersect for Sphere { None } } + +impl std::fmt::Display for Sphere { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "(sphere center: {}, radius: {}, material: {})", + self.center, self.radius, self.material + ) + } +} + +impl PartialOrd for Sphere { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} diff --git a/src/raytracer/types.rs b/src/raytracer/types.rs index fb197af..732775d 100644 --- a/src/raytracer/types.rs +++ b/src/raytracer/types.rs @@ -1,3 +1,7 @@ +use std::fmt::{Debug, Display}; + +use as_any::AsAny; + extern crate nalgebra as na; /// The Scalar type to use for raytracing (f32 may result in acne effects) @@ -16,10 +20,11 @@ pub trait Intersect { /// Otherwise the intersection point, a normal vector at the intersection point, /// the distance from the ray origin to the intersection point and /// the material of the object are returned. - fn intersect<'a>(&'a self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, &'a Material)>; + fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)>; } /// A point light source +#[derive(Clone, Debug, PartialEq, Copy)] pub struct Light { /// Position of the light source pub position: Point3, @@ -34,6 +39,12 @@ impl Light { } } +impl PartialOrd for Light { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} + /// A ray with origin and direction pub struct Ray { /// Ray origin @@ -50,6 +61,7 @@ impl Ray { } /// A Material used for PHONG shading +#[derive(Clone, Debug, PartialEq, Copy)] pub struct Material { /// Ambient color, aka color without direct or indirect light pub ambient_color: Color, @@ -86,3 +98,145 @@ impl Material { } } } + +impl PartialOrd for Material { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} + +//////// Display traits //////////////////////////////////////////////////////////////////////////// + +impl Display for Light { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "(light position: {}, color: {})", + self.position, self.color + ) + } +} + +impl Display for Material { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + "(material ambient_color: {}, diffuse_color: {}, specular_color: {}, shininess: {}, mirror: {})", + self.ambient_color, self.diffuse_color, self.specular_color, self.shininess, self.mirror + ) + } +} + +// RTWrapper /////////////////////////////////////////////////////////////////////////////////////// + +/// A trait used for Objects, which can be stored inside of the Scene, are Intersectable and are ForeignData compatible. +pub trait RTObject: Intersect + Display + Debug + AsAny + Sync + Send + 'static { + /// Convert the object to a Box allowing downcasts to Self + fn as_any_box(self: Box) -> Box; + /// Explicitly compare the object with another RTObject for object safety + fn eq_impl(&self, other: &dyn RTObject) -> bool; + /// Explicitly clone the object for object safety + fn clone_impl(&self) -> Box; +} + +impl RTObject for T { + fn as_any_box(self: Box) -> Box { + self + } + fn eq_impl(&self, other: &dyn RTObject) -> bool { + if let Some(other) = other.as_any().downcast_ref::() { + self == other + } else { + false + } + } + fn clone_impl(&self) -> Box { + Box::new(self.clone()) + } +} + +impl PartialEq for dyn RTObject { + fn eq(&self, other: &Self) -> bool { + self.eq_impl(other) + } +} + +/// The RTObjectWrapper is a wrapper around a Box to make it ForeignData compatible +/// (not depending on the concrete type of the object). +pub struct RTObjectWrapper(Box); + +impl RTObjectWrapper { + /// Create a new RTObjectWrapper from a Box + pub fn new(value: Box) -> RTObjectWrapper { + RTObjectWrapper(value) + } + /// Create a new RTObjectWrapper from a RTObject + pub fn from(value: T) -> RTObjectWrapper { + RTObjectWrapper::new(Box::new(value)) + } + /// Get the inner box as Box allowing downcasts to the concrete type + pub fn as_any_box(self) -> Box { + self.0.as_any_box() + } +} + +impl Clone for RTObjectWrapper { + fn clone(&self) -> Self { + RTObjectWrapper(self.0.clone_impl()) + } +} + +impl PartialEq for RTObjectWrapper { + fn eq(&self, other: &Self) -> bool { + *self.0 == *other.0 + } +} + +impl Display for RTObjectWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "RTObjectWrapper({})", self.0) + } +} + +impl Debug for RTObjectWrapper { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "RTObjectWrapper({:?})", self.0) + } +} + +impl Intersect for RTObjectWrapper { + fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> { + self.0.intersect(ray) + } +} + +impl PartialOrd for RTObjectWrapper { + fn partial_cmp(&self, _other: &Self) -> Option { + None + } +} + +#[test] +fn test_rt_wrapper_expr_conversion() { + use super::sphere::Sphere; + use crate::lisp::expression::{Expression, ForeignDataWrapper}; + let sphere = Sphere::new( + Point3::new(0.0, 0.0, 0.0), + 1.0, + Material::new( + Color::new(0.0, 0.0, 0.0), + Color::new(0.0, 0.0, 0.0), + Color::new(0.0, 0.0, 0.0), + 0.0, + 0.0, + ), + ); + + let sphere = RTObjectWrapper::new(Box::new(sphere)); + + let expr: Expression = ForeignDataWrapper::new(sphere.clone()).into(); + + let sphere2: ForeignDataWrapper = expr.try_into().unwrap(); + + assert_eq!(sphere, *sphere2.0); +}