feat(raytracer): add native rt functions to lisp
This commit is contained in:
parent
4b227fdd28
commit
6a3348d727
10
Cargo.toml
10
Cargo.toml
@ -1,6 +1,6 @@
|
|||||||
[package]
|
[package]
|
||||||
name = "lispers"
|
name = "lispers"
|
||||||
description = "lisp interpreter in rust"
|
description = "lisp interpreter in rust for raytracing"
|
||||||
publish = false
|
publish = false
|
||||||
version = "0.1.0"
|
version = "0.1.0"
|
||||||
|
|
||||||
@ -11,13 +11,17 @@ name = "lispers"
|
|||||||
path = "src/lib.rs"
|
path = "src/lib.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "demo"
|
name = "lisp_demo"
|
||||||
path = "src/bin/demo.rs"
|
path = "src/bin/lisp_demo.rs"
|
||||||
|
|
||||||
[[bin]]
|
[[bin]]
|
||||||
name = "repl"
|
name = "repl"
|
||||||
path = "src/bin/repl.rs"
|
path = "src/bin/repl.rs"
|
||||||
|
|
||||||
|
[[bin]]
|
||||||
|
name = "rt_lisp_demo"
|
||||||
|
path = "src/bin/rt_lisp_demo.rs"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
as-any = "0.3.1"
|
as-any = "0.3.1"
|
||||||
futures = "0.3.30"
|
futures = "0.3.30"
|
||||||
|
|||||||
10
flake.nix
10
flake.nix
@ -63,9 +63,9 @@
|
|||||||
};
|
};
|
||||||
in rec {
|
in rec {
|
||||||
apps = rec {
|
apps = rec {
|
||||||
demo = {
|
lisp_demo = {
|
||||||
type = "app";
|
type = "app";
|
||||||
program = "${packages.default}/bin/demo";
|
program = "${packages.default}/bin/lisp_demo";
|
||||||
};
|
};
|
||||||
repl = {
|
repl = {
|
||||||
type = "app";
|
type = "app";
|
||||||
@ -75,7 +75,11 @@
|
|||||||
type = "app";
|
type = "app";
|
||||||
program = "${packages.default}/bin/rt_demo";
|
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 {
|
packages = rec {
|
||||||
lispers = cargoNix.rootCrate.build;
|
lispers = cargoNix.rootCrate.build;
|
||||||
|
|||||||
@ -3,10 +3,9 @@ use lispers::raytracer::{
|
|||||||
plane::Checkerboard,
|
plane::Checkerboard,
|
||||||
scene::Scene,
|
scene::Scene,
|
||||||
sphere::Sphere,
|
sphere::Sphere,
|
||||||
types::{Color, Light, Material, Point3, Vector3},
|
types::{Color, Light, Material, Point3, RTObjectWrapper, Vector3},
|
||||||
};
|
};
|
||||||
extern crate nalgebra as na;
|
extern crate nalgebra as na;
|
||||||
use std::sync::Arc;
|
|
||||||
use std::time::Instant;
|
use std::time::Instant;
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@ -23,7 +22,7 @@ fn main() {
|
|||||||
color: Color::new(1.0, 1.0, 1.0),
|
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),
|
Point3::new(0.0, -1.0, 0.0),
|
||||||
Vector3::new(0.0, 1.0, 0.0),
|
Vector3::new(0.0, 1.0, 0.0),
|
||||||
Material::new(
|
Material::new(
|
||||||
@ -42,9 +41,9 @@ fn main() {
|
|||||||
),
|
),
|
||||||
0.3,
|
0.3,
|
||||||
Vector3::new(0.0, 0.0, 1.0),
|
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),
|
Point3::new(-2.0, 0.0, 1.0),
|
||||||
1.0,
|
1.0,
|
||||||
Material::new(
|
Material::new(
|
||||||
@ -54,9 +53,9 @@ fn main() {
|
|||||||
20.0,
|
20.0,
|
||||||
0.3,
|
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),
|
Point3::new(0.2, -0.5, -0.2),
|
||||||
0.5,
|
0.5,
|
||||||
Material::new(
|
Material::new(
|
||||||
@ -66,9 +65,9 @@ fn main() {
|
|||||||
20.0,
|
20.0,
|
||||||
0.3,
|
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),
|
Point3::new(-0.5, 0.5, -2.0),
|
||||||
1.5,
|
1.5,
|
||||||
Material::new(
|
Material::new(
|
||||||
@ -78,7 +77,7 @@ fn main() {
|
|||||||
20.0,
|
20.0,
|
||||||
0.3,
|
0.3,
|
||||||
),
|
),
|
||||||
)));
|
))));
|
||||||
|
|
||||||
let camera = Camera::new(
|
let camera = Camera::new(
|
||||||
Point3::new(0.0, 0.7, 5.0),
|
Point3::new(0.0, 0.7, 5.0),
|
||||||
|
|||||||
46
src/bin/rt_lisp_demo.rs
Normal file
46
src/bin/rt_lisp_demo.rs
Normal 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!");
|
||||||
|
}
|
||||||
@ -1,3 +1,5 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
scene::Scene,
|
scene::Scene,
|
||||||
types::{Color, Point3, Ray, Scalar, Vector3},
|
types::{Color, Point3, Ray, Scalar, Vector3},
|
||||||
@ -6,6 +8,7 @@ use image::RgbImage;
|
|||||||
use rayon::prelude::*;
|
use rayon::prelude::*;
|
||||||
|
|
||||||
/// A camera that can render a scene.
|
/// A camera that can render a scene.
|
||||||
|
#[derive(Clone, PartialEq, Debug)]
|
||||||
pub struct Camera {
|
pub struct Camera {
|
||||||
/// Position of the camera's eye.
|
/// Position of the camera's eye.
|
||||||
position: Point3,
|
position: Point3,
|
||||||
@ -106,3 +109,16 @@ impl Camera {
|
|||||||
img
|
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
226
src/raytracer/lisp.rs
Normal 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));
|
||||||
|
}
|
||||||
@ -1,4 +1,5 @@
|
|||||||
pub mod camera;
|
pub mod camera;
|
||||||
|
pub mod lisp;
|
||||||
pub mod plane;
|
pub mod plane;
|
||||||
pub mod scene;
|
pub mod scene;
|
||||||
pub mod sphere;
|
pub mod sphere;
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use super::types::{Intersect, Material, Point3, Scalar, Vector3};
|
|||||||
extern crate nalgebra as na;
|
extern crate nalgebra as na;
|
||||||
|
|
||||||
/// An infinite plane in 3D space.
|
/// An infinite plane in 3D space.
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub struct Plane {
|
pub struct Plane {
|
||||||
/// The position of the plane.
|
/// The position of the plane.
|
||||||
position: Point3,
|
position: Point3,
|
||||||
@ -13,6 +14,7 @@ pub struct Plane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A infinite checkerboard plane in 3D space.
|
/// A infinite checkerboard plane in 3D space.
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub struct Checkerboard {
|
pub struct Checkerboard {
|
||||||
/// The base plane containing the "white" material
|
/// The base plane containing the "white" material
|
||||||
base: Plane,
|
base: Plane,
|
||||||
@ -65,14 +67,14 @@ impl Checkerboard {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Intersect for Plane {
|
impl Intersect for Plane {
|
||||||
fn intersect<'a>(
|
fn intersect(
|
||||||
&'a self,
|
&self,
|
||||||
ray: &super::types::Ray,
|
ray: &super::types::Ray,
|
||||||
) -> Option<(
|
) -> Option<(
|
||||||
Point3,
|
Point3,
|
||||||
Vector3,
|
Vector3,
|
||||||
super::types::Scalar,
|
super::types::Scalar,
|
||||||
&'a super::types::Material,
|
super::types::Material,
|
||||||
)> {
|
)> {
|
||||||
let denom = self.normal.dot(&ray.direction);
|
let denom = self.normal.dot(&ray.direction);
|
||||||
if denom != 0.0 {
|
if denom != 0.0 {
|
||||||
@ -81,7 +83,7 @@ impl Intersect for Plane {
|
|||||||
|
|
||||||
if t > 1e-5 {
|
if t > 1e-5 {
|
||||||
let point = ray.origin + ray.direction * t;
|
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
|
None
|
||||||
@ -89,14 +91,14 @@ impl Intersect for Plane {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl Intersect for Checkerboard {
|
impl Intersect for Checkerboard {
|
||||||
fn intersect<'a>(
|
fn intersect(
|
||||||
&'a self,
|
&self,
|
||||||
ray: &super::types::Ray,
|
ray: &super::types::Ray,
|
||||||
) -> Option<(
|
) -> Option<(
|
||||||
Point3,
|
Point3,
|
||||||
Vector3,
|
Vector3,
|
||||||
super::types::Scalar,
|
super::types::Scalar,
|
||||||
&'a super::types::Material,
|
super::types::Material,
|
||||||
)> {
|
)> {
|
||||||
if let Some((point, normal, t, material)) = self.base.intersect(ray) {
|
if let Some((point, normal, t, material)) = self.base.intersect(ray) {
|
||||||
let v3 = point - self.base.position;
|
let v3 = point - self.base.position;
|
||||||
@ -105,12 +107,44 @@ impl Intersect for Checkerboard {
|
|||||||
if ((v2.x / self.scale).round() % 2.0 == 0.0)
|
if ((v2.x / self.scale).round() % 2.0 == 0.0)
|
||||||
== ((v2.y / 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 {
|
} else {
|
||||||
Some((point, normal, t, &self.material_alt))
|
Some((point, normal, t, self.material_alt.clone()))
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
None
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,21 +1,24 @@
|
|||||||
|
use std::fmt::Display;
|
||||||
|
|
||||||
use super::types::Color;
|
use super::types::Color;
|
||||||
use super::types::Intersect;
|
use super::types::Intersect;
|
||||||
use super::types::Light;
|
use super::types::Light;
|
||||||
use super::types::Material;
|
use super::types::Material;
|
||||||
use super::types::Point3;
|
use super::types::Point3;
|
||||||
|
use super::types::RTObjectWrapper;
|
||||||
use super::types::Ray;
|
use super::types::Ray;
|
||||||
use super::types::Vector3;
|
use super::types::Vector3;
|
||||||
use super::vec::mirror;
|
use super::vec::mirror;
|
||||||
use super::vec::reflect;
|
use super::vec::reflect;
|
||||||
use std::sync::Arc;
|
|
||||||
extern crate nalgebra as na;
|
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.
|
/// 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 {
|
pub struct Scene {
|
||||||
/// The ambient light of the scene
|
/// The ambient light of the scene
|
||||||
ambient: Color,
|
ambient: Color,
|
||||||
/// The objects in the scene
|
/// The objects in the scene
|
||||||
objects: Vec<Arc<dyn Intersect + Send + Sync>>,
|
objects: Vec<RTObjectWrapper>,
|
||||||
/// The lights in the scene
|
/// The lights in the scene
|
||||||
lights: Vec<Light>,
|
lights: Vec<Light>,
|
||||||
}
|
}
|
||||||
@ -36,7 +39,7 @@ impl Scene {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Add an object to the 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);
|
self.objects.push(obj);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -61,7 +64,7 @@ impl Scene {
|
|||||||
{
|
{
|
||||||
Some((isect_pt, isect_norm, _, material)) => {
|
Some((isect_pt, isect_norm, _, material)) => {
|
||||||
// Lighting of material at the intersection point
|
// 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
|
// Calculate reflections, if the material has mirror properties
|
||||||
if material.mirror > 0.0 {
|
if material.mirror > 0.0 {
|
||||||
@ -128,3 +131,21 @@ impl Scene {
|
|||||||
color
|
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()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,6 +3,7 @@ use super::types::{Intersect, Material, Point3, Ray, Scalar, Vector3};
|
|||||||
extern crate nalgebra as na;
|
extern crate nalgebra as na;
|
||||||
|
|
||||||
/// A sphere in 3D space
|
/// A sphere in 3D space
|
||||||
|
#[derive(PartialEq, Clone, Debug)]
|
||||||
pub struct Sphere {
|
pub struct Sphere {
|
||||||
/// Center of the sphere
|
/// Center of the sphere
|
||||||
center: Point3,
|
center: Point3,
|
||||||
@ -27,7 +28,7 @@ impl Sphere {
|
|||||||
const EPSILON: Scalar = 1e-5;
|
const EPSILON: Scalar = 1e-5;
|
||||||
|
|
||||||
impl Intersect for Sphere {
|
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 co = ray.origin - self.center;
|
||||||
|
|
||||||
let a = ray.direction.dot(&ray.direction);
|
let a = ray.direction.dot(&ray.direction);
|
||||||
@ -55,7 +56,7 @@ impl Intersect for Sphere {
|
|||||||
isect_pt,
|
isect_pt,
|
||||||
(isect_pt - self.center) / self.radius,
|
(isect_pt - self.center) / self.radius,
|
||||||
t,
|
t,
|
||||||
&self.material,
|
self.material.clone(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -63,3 +64,19 @@ impl Intersect for Sphere {
|
|||||||
None
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -1,3 +1,7 @@
|
|||||||
|
use std::fmt::{Debug, Display};
|
||||||
|
|
||||||
|
use as_any::AsAny;
|
||||||
|
|
||||||
extern crate nalgebra as na;
|
extern crate nalgebra as na;
|
||||||
|
|
||||||
/// The Scalar type to use for raytracing (f32 may result in acne effects)
|
/// 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,
|
/// Otherwise the intersection point, a normal vector at the intersection point,
|
||||||
/// the distance from the ray origin to the intersection point and
|
/// the distance from the ray origin to the intersection point and
|
||||||
/// the material of the object are returned.
|
/// 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
|
/// A point light source
|
||||||
|
#[derive(Clone, Debug, PartialEq, Copy)]
|
||||||
pub struct Light {
|
pub struct Light {
|
||||||
/// Position of the light source
|
/// Position of the light source
|
||||||
pub position: Point3,
|
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
|
/// A ray with origin and direction
|
||||||
pub struct Ray {
|
pub struct Ray {
|
||||||
/// Ray origin
|
/// Ray origin
|
||||||
@ -50,6 +61,7 @@ impl Ray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A Material used for PHONG shading
|
/// A Material used for PHONG shading
|
||||||
|
#[derive(Clone, Debug, PartialEq, Copy)]
|
||||||
pub struct Material {
|
pub struct Material {
|
||||||
/// Ambient color, aka color without direct or indirect light
|
/// Ambient color, aka color without direct or indirect light
|
||||||
pub ambient_color: Color,
|
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);
|
||||||
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user