feat(raytracer): add native rt functions to lisp

This commit is contained in:
Jonas Röger 2024-11-28 01:57:40 +01:00
parent 4b227fdd28
commit 6a3348d727
Signed by: jonas
GPG Key ID: 4000EB35E1AE0F07
11 changed files with 554 additions and 32 deletions

View File

@ -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"

View File

@ -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;

View File

@ -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),

46
src/bin/rt_lisp_demo.rs Normal file
View File

@ -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!");
}

View File

@ -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<std::cmp::Ordering> {
None
}
}

226
src/raytracer/lisp.rs Normal file
View File

@ -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<Expression, EvalError> {
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<Expression, EvalError> {
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<Expression, EvalError> {
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<Expression, EvalError> {
let [pos, col]: [Expression; 2] = expr.try_into()?;
let pos: ForeignDataWrapper<Point3> = eval(env, pos)?.try_into()?;
let col: ForeignDataWrapper<Color> = 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<Expression, EvalError> {
let [amb, dif, spe, shi, mir]: [Expression; 5] = expr.try_into()?;
let amb: ForeignDataWrapper<Color> = eval(env, amb)?.try_into()?;
let dif: ForeignDataWrapper<Color> = eval(env, dif)?.try_into()?;
let spe: ForeignDataWrapper<Color> = 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<Expression, EvalError> {
let [pos, rad, mat]: [Expression; 3] = expr.try_into()?;
let pos: ForeignDataWrapper<Point3> = eval(env, pos)?.try_into()?;
let rad: f64 = eval(env, rad)?.try_into()?;
let mat: ForeignDataWrapper<Material> = 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<Expression, EvalError> {
let [pos, dir, mat]: [Expression; 3] = expr.try_into()?;
let pos: ForeignDataWrapper<Point3> = eval(env, pos)?.try_into()?;
let dir: ForeignDataWrapper<Vector3> = eval(env, dir)?.try_into()?;
let mat: ForeignDataWrapper<Material> = 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<Expression, EvalError> {
let [pos, norm, mat1, mat2, sca, up]: [Expression; 6] = expr.try_into()?;
let pos: ForeignDataWrapper<Point3> = eval(env, pos)?.try_into()?;
let norm: ForeignDataWrapper<Vector3> = eval(env, norm)?.try_into()?;
let mat1: ForeignDataWrapper<Material> = eval(env, mat1)?.try_into()?;
let mat2: ForeignDataWrapper<Material> = eval(env, mat2)?.try_into()?;
let sca: f64 = eval(env, sca)?.try_into()?;
let up: ForeignDataWrapper<Vector3> = 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<Expression, EvalError> {
let [amb, objs, lgts]: [Expression; 3] = expr.try_into()?;
let amb: ForeignDataWrapper<Color> = eval(env, amb)?.try_into()?;
let objs: Vec<Expression> = eval(env, objs)?.try_into()?;
let lgts: Vec<Expression> = eval(env, lgts)?.try_into()?;
let mut scene = Scene::new();
scene.set_ambient(*amb);
for o in objs {
let o: ForeignDataWrapper<RTObjectWrapper> = eval(env, o)?.try_into()?;
scene.add_object(o.clone());
}
for l in lgts {
let l: ForeignDataWrapper<Light> = eval(env, l)?.try_into()?;
scene.add_light(*l);
}
Ok(ForeignDataWrapper::new(scene).into())
}
pub fn scene_add_object(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [sce, obj]: [Expression; 2] = expr.try_into()?;
let mut sce: ForeignDataWrapper<Scene> = eval(env, sce)?.try_into()?;
let obj: ForeignDataWrapper<RTObjectWrapper> = eval(env, obj)?.try_into()?;
sce.add_object(obj.clone());
Ok(sce.into())
}
pub fn scene_add_light(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [sce, lgt]: [Expression; 2] = expr.try_into()?;
let mut sce: ForeignDataWrapper<Scene> = eval(env, sce)?.try_into()?;
let lgt: ForeignDataWrapper<Light> = eval(env, lgt)?.try_into()?;
sce.add_light(*lgt);
Ok(sce.into())
}
pub fn camera(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [pos, cnt, up, fovy, w, h]: [Expression; 6] = expr.try_into()?;
let pos: ForeignDataWrapper<Point3> = eval(env, pos)?.try_into()?;
let cnt: ForeignDataWrapper<Point3> = eval(env, cnt)?.try_into()?;
let up: ForeignDataWrapper<Vector3> = 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<Expression, EvalError> {
let [cam, sce, dpt, sbp, out]: [Expression; 5] = expr.try_into()?;
let cam: ForeignDataWrapper<Camera> = eval(env, cam)?.try_into()?;
let sce: ForeignDataWrapper<Scene> = 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));
}

View File

@ -1,4 +1,5 @@
pub mod camera;
pub mod lisp;
pub mod plane;
pub mod scene;
pub mod sphere;

View File

@ -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<std::cmp::Ordering> {
None
}
}
impl PartialOrd for Checkerboard {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}

View File

@ -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<Arc<dyn Intersect + Send + Sync>>,
objects: Vec<RTObjectWrapper>,
/// The lights in the scene
lights: Vec<Light>,
}
@ -36,7 +39,7 @@ impl Scene {
}
/// Add an object to the scene
pub fn add_object(&mut self, obj: Arc<dyn Intersect + Send + Sync>) {
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<std::cmp::Ordering> {
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()
)
}
}

View File

@ -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<std::cmp::Ordering> {
None
}
}

View File

@ -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<std::cmp::Ordering> {
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<std::cmp::Ordering> {
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<dyn Any> allowing downcasts to Self
fn as_any_box(self: Box<Self>) -> Box<dyn std::any::Any>;
/// 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<dyn RTObject>;
}
impl<T: Intersect + Display + Debug + PartialEq + Clone + Sync + Send + 'static> RTObject for T {
fn as_any_box(self: Box<Self>) -> Box<dyn std::any::Any> {
self
}
fn eq_impl(&self, other: &dyn RTObject) -> bool {
if let Some(other) = other.as_any().downcast_ref::<T>() {
self == other
} else {
false
}
}
fn clone_impl(&self) -> Box<dyn RTObject> {
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<dyn RTObject> to make it ForeignData compatible
/// (not depending on the concrete type of the object).
pub struct RTObjectWrapper(Box<dyn RTObject>);
impl RTObjectWrapper {
/// Create a new RTObjectWrapper from a Box<dyn RTObject>
pub fn new<T: RTObject>(value: Box<T>) -> RTObjectWrapper {
RTObjectWrapper(value)
}
/// Create a new RTObjectWrapper from a RTObject
pub fn from<T: RTObject>(value: T) -> RTObjectWrapper {
RTObjectWrapper::new(Box::new(value))
}
/// Get the inner box as Box<dyn Any> allowing downcasts to the concrete type
pub fn as_any_box(self) -> Box<dyn std::any::Any> {
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<std::cmp::Ordering> {
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<RTObjectWrapper> = expr.try_into().unwrap();
assert_eq!(sphere, *sphere2.0);
}