Compare commits

...

10 Commits

Author SHA1 Message Date
d835dc48ce
feat(prelude): add list/string functions 2025-01-12 21:47:06 +01:00
e770e6f8a7
build(flake): add rt_interp binary 2025-01-11 17:09:45 +01:00
b38e6c00a5
feat(rtlisp): add rt_inerp binary and demo script 2025-01-11 16:35:09 +01:00
1871f6cae4
feat(rtlisp): clean up rt lisp functions
- sin/cos
- scene-add proxy
2025-01-11 16:33:05 +01:00
88bbcf036f
feat(core): improve printing
- Expression Conversion
- print/println
2025-01-11 16:31:41 +01:00
ad0792dcd3
feat(lisp): add overloadable native functions 2025-01-07 00:55:03 +01:00
1856de7685
build(flake): adjust cargoNix to workspace 2025-01-04 21:03:49 +01:00
3e11142361
feat: add native_lisp_function macro
- refactor project layout to use child crates
  - lispers-core: parser and evaluator
  - lispers-macro: proc macros
2025-01-04 20:12:11 +01:00
9179f06132
fix(build): binary names 2024-11-28 02:05:02 +01:00
6a3348d727
feat(raytracer): add native rt functions to lisp 2024-11-28 01:57:40 +01:00
31 changed files with 1148 additions and 94 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
.direnv/
target/
result/
*.png

119
Cargo.lock generated
View File

@ -16,9 +16,9 @@ checksum = "4aa90d7ce82d4be67b64039a3d588d38dbcc6736577de4a847025ce5b0c468d1"
[[package]]
name = "anyhow"
version = "1.0.93"
version = "1.0.95"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4c95c10ba0b00a02636238b814946408b1322d5ac4760326e6fb8ec956d85775"
checksum = "34ac096ce696dc2fcabef30516bb13c0a68a11d30131d3df6f04711467681b04"
[[package]]
name = "approx"
@ -125,9 +125,9 @@ checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c"
[[package]]
name = "bytemuck"
version = "1.20.0"
version = "1.21.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8b37c88a63ffd85d15b406896cc343916d7cf57838a847b3a6f2ca5d39a5695a"
checksum = "ef657dfab802224e671f5818e9a4935f9b1957ed18e58292690cc39e7a4092a3"
[[package]]
name = "byteorder"
@ -143,9 +143,9 @@ checksum = "8f1fe948ff07f4bd06c30984e69f5b4899c516a3ef74f34df92a2df2ab535495"
[[package]]
name = "cc"
version = "1.2.1"
version = "1.2.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fd9de9f2205d5ef3fd67e685b0df337994ddd4495e2a28d185500d0e1edfea47"
checksum = "a012a0df96dd6d06ba9a1b29d6402d1a5d77c6befd2566afdc26e10603dc93d7"
dependencies = [
"jobserver",
"libc",
@ -191,9 +191,9 @@ dependencies = [
[[package]]
name = "crossbeam-deque"
version = "0.8.5"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "613f8cc01fe9cf1a3eb3d7f488fd2fa8388403e97039e2f73692932e291a770d"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
@ -210,9 +210,9 @@ dependencies = [
[[package]]
name = "crossbeam-utils"
version = "0.8.20"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "22ec99545bb0ed0ea7bb9b8e1e9122ea386ff8a48c0922e43f36d45ab09e0e80"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crunchy"
@ -249,9 +249,9 @@ dependencies = [
[[package]]
name = "fdeflate"
version = "0.3.6"
version = "0.3.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "07c6f4c64c1d33a3111c4466f7365ebdcc37c5bd1ea0d62aae2e3d722aacbedb"
checksum = "1e6853b52649d4ac5c0bd02320cddc5ba956bdb407c4b75a2c6b75bf51500f8c"
dependencies = [
"simd-adler32",
]
@ -388,9 +388,9 @@ dependencies = [
[[package]]
name = "hashbrown"
version = "0.15.1"
version = "0.15.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3a9bfc1af68b1726ea47d3d5109de126281def866b33970e10fbab11b5dafab3"
checksum = "bf151400ff0baff5465007dd2f3e717f3fe502074ca563069ce3a6629d07b289"
[[package]]
name = "heck"
@ -439,9 +439,9 @@ checksum = "d0263a3d970d5c054ed9312c0057b4f3bde9c0b33836d3637361d4a9e6e7a408"
[[package]]
name = "indexmap"
version = "2.6.0"
version = "2.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "707907fe3c25f5424cce2cb7e1cbcafee6bdbe735ca90ef77c29e84591e5b9da"
checksum = "62f822373a4fe84d4bb149bf54e584a7f4abec90e072ed49cda0edea5b95471f"
dependencies = [
"equivalent",
"hashbrown",
@ -490,9 +490,9 @@ checksum = "03087c2bad5e1034e8cace5926dec053fb3790248370865f5117a7d0213354c8"
[[package]]
name = "libc"
version = "0.2.164"
version = "0.2.169"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f"
checksum = "b5aba8db14291edd000dfcc4d620c7ebfb122c613afb886ca8803fa4e128a20a"
[[package]]
name = "libfuzzer-sys"
@ -511,11 +511,29 @@ dependencies = [
"as-any",
"futures",
"image",
"lispers-core",
"lispers-macro",
"nalgebra",
"nix",
"rayon",
]
[[package]]
name = "lispers-core"
version = "0.1.0"
dependencies = [
"as-any",
]
[[package]]
name = "lispers-macro"
version = "0.1.0"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]]
name = "log"
version = "0.4.22"
@ -565,9 +583,9 @@ checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a"
[[package]]
name = "miniz_oxide"
version = "0.8.0"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e2d80299ef12ff69b16a84bb182e3b9df68b5a91574d3d4fa6e41b65deec4df1"
checksum = "4ffbe83022cedc1d264172192511ae958937694cd57ce297164951b8b3568394"
dependencies = [
"adler2",
"simd-adler32",
@ -725,9 +743,9 @@ checksum = "953ec861398dccce10c670dfeaf3ec4911ca479e9c02154b3a215178c5f566f2"
[[package]]
name = "png"
version = "0.17.14"
version = "0.17.16"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "52f9d46a34a05a6a57566bc2bfae066ef07585a6e3fa30fbbdff5936380623f0"
checksum = "82151a2fc869e011c153adc57cf2789ccb8d9906ce52c0b39a6b5697749d7526"
dependencies = [
"bitflags 1.3.2",
"crc32fast",
@ -747,9 +765,9 @@ dependencies = [
[[package]]
name = "proc-macro2"
version = "1.0.89"
version = "1.0.92"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f139b0662de085916d1fb67d2b4169d1addddda1919e696f3252b740b629986e"
checksum = "37d3544b3f2748c54e147655edb5025752e2303145b5aefb3c3ea2c78b973bb0"
dependencies = [
"unicode-ident",
]
@ -790,9 +808,9 @@ checksum = "a993555f31e5a609f617c12db6250dedcac1b0a85076912c436e6fc9b2c8e6a3"
[[package]]
name = "quote"
version = "1.0.37"
version = "1.0.38"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af"
checksum = "0e4dccaaaf89514f546c693ddc140f729f958c247918a13380cccc6078391acc"
dependencies = [
"proc-macro2",
]
@ -911,27 +929,27 @@ checksum = "57397d16646700483b67d2dd6511d79318f9d057fdbd21a4066aeac8b41d310a"
[[package]]
name = "safe_arch"
version = "0.7.2"
version = "0.7.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3460605018fdc9612bce72735cba0d27efbcd9904780d44c7e3a9948f96148a"
checksum = "96b02de82ddbe1b636e6170c21be622223aea188ef2e139be0a5b219ec215323"
dependencies = [
"bytemuck",
]
[[package]]
name = "serde"
version = "1.0.215"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f"
checksum = "02fc4265df13d6fa1d00ecff087228cc0a2b5f3c0e87e258d8b94a156e984c70"
dependencies = [
"serde_derive",
]
[[package]]
name = "serde_derive"
version = "1.0.215"
version = "1.0.217"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0"
checksum = "5a9bf7cf98d04a2b28aead066b7496853d4779c9cc183c440dbac457641e19a0"
dependencies = [
"proc-macro2",
"quote",
@ -998,9 +1016,9 @@ checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67"
[[package]]
name = "syn"
version = "2.0.87"
version = "2.0.94"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d"
checksum = "987bc0be1cdea8b10216bd06e2ca407d40b9543468fafd3ddfb02f36e77f71f3"
dependencies = [
"proc-macro2",
"quote",
@ -1128,9 +1146,9 @@ checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423"
[[package]]
name = "wasm-bindgen"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "128d1e363af62632b8eb57219c8fd7877144af57558fb2ef0368d0087bddeb2e"
checksum = "a474f6281d1d70c17ae7aa6a613c87fce69a127e2624002df63dcb39d6cf6396"
dependencies = [
"cfg-if",
"once_cell",
@ -1139,13 +1157,12 @@ dependencies = [
[[package]]
name = "wasm-bindgen-backend"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cb6dd4d3ca0ddffd1dd1c9c04f94b868c37ff5fac97c30b97cff2d74fce3a358"
checksum = "5f89bb38646b4f81674e8f5c3fb81b562be1fd936d84320f3264486418519c79"
dependencies = [
"bumpalo",
"log",
"once_cell",
"proc-macro2",
"quote",
"syn",
@ -1154,9 +1171,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e79384be7f8f5a9dd5d7167216f022090cf1f9ec128e6e6a482a2cb5c5422c56"
checksum = "2cc6181fd9a7492eef6fef1f33961e3695e4579b9872a6f7c83aee556666d4fe"
dependencies = [
"quote",
"wasm-bindgen-macro-support",
@ -1164,9 +1181,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-macro-support"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "26c6ab57572f7a24a4985830b120de1594465e5d500f24afe89e16b4e833ef68"
checksum = "30d7a95b763d3c45903ed6c81f156801839e5ee968bb07e534c44df0fcd330c2"
dependencies = [
"proc-macro2",
"quote",
@ -1177,9 +1194,9 @@ dependencies = [
[[package]]
name = "wasm-bindgen-shared"
version = "0.2.95"
version = "0.2.99"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "65fc09f10666a9f147042251e0dda9c18f166ff7de300607007e96bdebc1068d"
checksum = "943aab3fdaaa029a6e0271b35ea10b72b943135afe9bffca82384098ad0e06a6"
[[package]]
name = "weezl"
@ -1189,9 +1206,9 @@ checksum = "53a85b86a771b1c87058196170769dd264f66c0782acf1ae6cc51bfd64b39082"
[[package]]
name = "wide"
version = "0.7.30"
version = "0.7.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "58e6db2670d2be78525979e9a5f9c69d296fd7d670549fe9ebf70f8708cb5019"
checksum = "cc0ca27312d1e9218687a4e804cd4b7410bf54125d54d13e5170efbf09066d24"
dependencies = [
"bytemuck",
"safe_arch",
@ -1199,9 +1216,9 @@ dependencies = [
[[package]]
name = "winnow"
version = "0.6.20"
version = "0.6.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "36c1fec1a2bb5866f07c25f68c26e565c4c200aebb96d7e55710c19d3e8ac49b"
checksum = "39281189af81c07ec09db316b302a3e67bf9bd7cbf6c820b50e35fee9c2fa980"
dependencies = [
"memchr",
]
@ -1244,9 +1261,9 @@ dependencies = [
[[package]]
name = "zune-jpeg"
version = "0.4.13"
version = "0.4.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "16099418600b4d8f028622f73ff6e3deaabdff330fb9a2a131dea781ee8b0768"
checksum = "99a5bab8d7dedf81405c4bb1f2b83ea057643d9cb28778cea9eecddeedd2e028"
dependencies = [
"zune-core",
]

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,17 +11,35 @@ 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"
[dependencies]
[[bin]]
name = "rt_lisp_demo"
path = "src/bin/rt_lisp_demo.rs"
[[bin]]
name = "rt_interp"
path = "src/bin/rt_interp.rs"
[workspace]
members = [ "lispers-core", "lispers-macro"]
[workspace.dependencies]
lispers-core = {path = "lispers-core"}
lispers-macro = {path = "lispers-macro"}
as-any = "0.3.1"
[dependencies]
as-any = {workspace = true}
futures = "0.3.30"
image = "0.25.5"
nalgebra = "0.33.2"
nix = "0.29.0"
rayon = "1.10.0"
lispers-core = {workspace = true}
lispers-macro = {workspace = true}

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,10 +75,18 @@
type = "app";
program = "${packages.default}/bin/rt_demo";
};
default = demo;
rt_demo_lisp = {
type = "app";
program = "${packages.default}/bin/rt_lisp_demo";
};
rt_interp = {
type = "app";
program = "${packages.default}/bin/rt_interp";
};
default = rt_demo_lisp;
};
packages = rec {
lispers = cargoNix.rootCrate.build;
lispers = cargoNix.workspaceMembers.lispers.build;
default = lispers;
};
devShell = pkgs.mkShell {

7
lispers-core/Cargo.toml Normal file
View File

@ -0,0 +1,7 @@
[package]
name = "lispers-core"
version = "0.1.0"
edition = "2021"
[dependencies]
as-any = {workspace = true}

2
lispers-core/src/lib.rs Normal file
View File

@ -0,0 +1,2 @@
pub mod lisp;
pub mod parser;

View File

@ -198,6 +198,34 @@ impl From<(Expression, Expression)> for Expression {
}
}
impl From<i64> for Expression {
fn from(value: i64) -> Self {
Expression::Integer(value)
}
}
impl From<f64> for Expression {
fn from(value: f64) -> Self {
Expression::Float(value)
}
}
impl From<String> for Expression {
fn from(value: String) -> Self {
Expression::String(value)
}
}
impl From<bool> for Expression {
fn from(value: bool) -> Self {
if value {
Expression::True
} else {
Expression::Nil
}
}
}
impl TryFrom<Expression> for i64 {
type Error = EvalError;
fn try_from(value: Expression) -> Result<i64, Self::Error> {
@ -321,7 +349,7 @@ impl Display for Expression {
Expression::Symbol(s) => write!(f, "{}", s),
Expression::Integer(i) => write!(f, "{}", i),
Expression::Float(fl) => write!(f, "{}", fl),
Expression::String(s) => write!(f, "\"{}\"", s),
Expression::String(s) => write!(f, "{}", s),
Expression::True => write!(f, "true"),
Expression::Nil => write!(f, "nil"),
}

View File

@ -213,10 +213,17 @@ pub fn prelude_set(env: &Environment, expr: Expression) -> Result<Expression, Ev
}
}
pub fn prelude_println(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [e] = expr.try_into()?;
let e = eval(env, e)?;
println!("{}", e);
Ok(e)
}
pub fn prelude_print(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [e] = expr.try_into()?;
let e = eval(env, e)?;
println!("Prelude: {}", e);
print!("{}", e);
Ok(e)
}
@ -255,6 +262,69 @@ pub fn prelude_progn(env: &Environment, expr: Expression) -> Result<Expression,
Ok(result)
}
pub fn prelude_list(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let exprs: Vec<Expression> = expr.try_into()?;
let evaled_exprs: Vec<_> = exprs
.iter()
.map(|e| eval(env, e.to_owned()))
.collect::<Result<_, _>>()?;
Ok(evaled_exprs.into())
}
pub fn prelude_append(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let exprs: Vec<Expression> = expr.try_into()?;
let evaled_exprs: Vec<_> = exprs
.iter()
.map(|e| eval(env, e.to_owned())?.try_into())
.collect::<Result<Vec<Vec<Expression>>, _>>()?;
Ok(evaled_exprs.concat().into())
}
pub fn prelude_concat(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let exprs: Vec<Expression> = expr.try_into()?;
let evaled_exprs: Vec<String> = exprs
.iter()
.map(|e| eval(env, e.to_owned())?.try_into())
.collect::<Result<_, _>>()?;
Ok(evaled_exprs.concat().into())
}
pub fn prelude_map(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [f, list]: [Expression; 2] = expr.try_into()?;
let f = eval(env, f)?;
let list: Vec<Expression> = eval(env, list)?.try_into()?;
let list: Vec<Expression> = list
.iter()
.map(|e| {
eval(
env,
Expression::Cell(
Box::new(f.clone()),
Box::new(Expression::Cell(
Box::new(e.to_owned()),
Box::new(Expression::Nil),
)),
),
)
})
.collect::<Result<_, _>>()?;
Ok(list.into())
}
pub fn prelude_to_string(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [e] = expr.try_into()?;
Ok(Expression::String(format!("{}", eval(env, e)?)))
}
pub fn mk_prelude(layer: &mut EnvironmentLayer) {
layer.set("+".to_string(), Expression::Function(prelude_add));
layer.set("-".to_string(), Expression::Function(prelude_sub));
@ -270,10 +340,19 @@ pub fn mk_prelude(layer: &mut EnvironmentLayer) {
layer.set("not".to_string(), Expression::Function(prelude_not));
layer.set("let".to_string(), Expression::Function(prelude_let));
layer.set("set".to_string(), Expression::Function(prelude_set));
layer.set("println".to_string(), Expression::Function(prelude_println));
layer.set("print".to_string(), Expression::Function(prelude_print));
layer.set("cons".to_string(), Expression::Function(prelude_cons));
layer.set("car".to_string(), Expression::Function(prelude_car));
layer.set("cdr".to_string(), Expression::Function(prelude_cdr));
layer.set("eval".to_string(), Expression::Function(prelude_eval));
layer.set("progn".to_string(), Expression::Function(prelude_progn));
layer.set("list".to_string(), Expression::Function(prelude_list));
layer.set("append".to_string(), Expression::Function(prelude_append));
layer.set("concat".to_string(), Expression::Function(prelude_concat));
layer.set("map".to_string(), Expression::Function(prelude_map));
layer.set(
"to-string".to_string(),
Expression::Function(prelude_to_string),
);
}

12
lispers-macro/Cargo.toml Normal file
View File

@ -0,0 +1,12 @@
[package]
name = "lispers-macro"
version = "0.1.0"
edition = "2021"
[lib]
proc-macro = true
[dependencies]
proc-macro2 = "1.0.92"
quote = "1.0.38"
syn = "2.0.94"

192
lispers-macro/src/lib.rs Normal file
View File

@ -0,0 +1,192 @@
extern crate proc_macro;
use proc_macro::TokenStream;
use quote::quote;
use syn::{parse_macro_input, punctuated::Punctuated, FnArg, Ident, ItemFn, Pat, PatType, Token};
enum FlagOrKV {
Flag(Ident),
KV(Ident, Ident),
}
impl syn::parse::Parse for FlagOrKV {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let ident: Ident = input.parse()?;
if input.peek(Token![=]) {
input.parse::<Token![=]>()?;
let value: Ident = input.parse()?;
Ok(FlagOrKV::KV(ident, value))
} else {
Ok(FlagOrKV::Flag(ident))
}
}
}
struct NativeLispAttrs {
pub eval: bool,
pub fname: Option<Ident>,
}
impl syn::parse::Parse for NativeLispAttrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let exprs = Punctuated::<FlagOrKV, Token![,]>::parse_terminated(input)?;
let mut ret = NativeLispAttrs {
eval: false,
fname: None,
};
for e in exprs {
match e {
FlagOrKV::Flag(flag) => {
if flag.to_string() == "eval" {
ret.eval = true;
} else {
return Err(syn::Error::new_spanned(flag, "Unknown flag"));
}
}
FlagOrKV::KV(k, v) => {
if k.to_string() == "fname" {
ret.fname = Some(v);
} else {
return Err(syn::Error::new_spanned(k, "Unknown key"));
}
}
}
}
Ok(ret)
}
}
struct NativeLispProxyAttrs {
pub eval: bool,
pub fname: Ident,
pub dispatcher: Vec<Ident>,
}
impl syn::parse::Parse for NativeLispProxyAttrs {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let exprs = Punctuated::<FlagOrKV, Token![,]>::parse_terminated(input)?;
let mut ret = NativeLispProxyAttrs {
eval: false,
fname: Ident::new("proxy", proc_macro2::Span::call_site()),
dispatcher: Vec::new(),
};
for e in exprs {
match e {
FlagOrKV::Flag(flag) => {
if flag.to_string() == "eval" {
ret.eval = true;
} else {
return Err(syn::Error::new_spanned(flag, "Unknown flag"));
}
}
FlagOrKV::KV(k, v) => {
if k.to_string() == "dispatch" {
ret.dispatcher.push(v);
} else if k.to_string() == "fname" {
ret.fname = v;
} else {
return Err(syn::Error::new_spanned(k, "Unknown key"));
}
}
}
}
Ok(ret)
}
}
#[proc_macro_attribute]
pub fn native_lisp_function(attr: TokenStream, item: TokenStream) -> TokenStream {
// Parse function
let input = parse_macro_input!(item as ItemFn);
let vis = &input.vis;
let sig = &input.sig;
let func_name = &sig.ident;
let block = &input.block;
let ret = &sig.output;
// Parse attrs
let attr = parse_macro_input!(attr as NativeLispAttrs);
// Extract argument conversion statements
let mut conversion_statements = Vec::new();
for arg in &sig.inputs {
if let FnArg::Typed(PatType { pat, ty, .. }) = arg {
if let Pat::Ident(ident) = pat.as_ref() {
let arg_name_str = ident.ident.to_string();
if attr.eval {
conversion_statements.push(quote! {
let #ident: #ty = eval(env, args_iter.next().ok_or(EvalError::ArgumentError(format!("Missing Argument {}", #arg_name_str)))?)?.try_into()?;
});
} else {
conversion_statements.push(quote! {
let #ident: #ty = args_iter.next().ok_or(EvalError::ArgumentError(format!("Missing Argument {}", #arg_name_str)))?.try_into()?;
});
}
}
}
}
let func_name = match attr.fname {
Some(fname) => fname,
None => func_name.clone(),
};
quote! {
#vis fn #func_name(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let args: Vec<Expression> = expr.try_into()?;
let mut args_iter = args.into_iter();
#(#conversion_statements)*
Ok((|| #ret #block)()?.into())
}
}
.into()
}
#[proc_macro]
pub fn native_lisp_function_proxy(item: TokenStream) -> TokenStream {
let args = parse_macro_input!(item as NativeLispProxyAttrs);
let fname = &args.fname;
let eval_statement = if args.eval {
quote! {
let exprs: Vec<Expression> = expr.try_into()?;
let exprs = exprs.into_iter().map(|expr| eval(env, expr)).collect::<Result<Vec<Expression>, EvalError>>()?;
let expr: Expression = exprs.into();
}
} else {
quote! {}
};
let try_apply_statements = args
.dispatcher
.iter()
.map(|impl_name| {
quote! {
match #impl_name(env, expr.clone()) {
Err(EvalError::ArgumentError(e)) => {/*Pass*/},
Err(EvalError::TypeError(e)) => {/*Pass*/},
x => return x,
}
}
})
.collect::<Vec<_>>();
quote! {
fn #fname(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
#eval_statement
#(#try_apply_statements)*
Err(EvalError::TypeError("No applicable method found".to_string()))
}
}
.into()
}

79
scenes/demo-1.lisp Normal file
View File

@ -0,0 +1,79 @@
(set 'red
(material
(color 1 0 0)
(color 1 0 0)
(color 0.5 0 0)
50 0.25))
(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))
(defun spiral-sphere (i n)
(sphere
(progn
(print "Spiral Sphere at: ")
(println (point
(* 2 (cos (/ (* i 6.2) n)))
0.5
(* 2 (sin (/ (* i 6.2) n)))))
)
0.5 red))
(defun spiral (scn i n)
(if (< i n)
(scene-add
(spiral scn (+ i 1) n)
(spiral-sphere i n))
scn))
(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 'scn (spiral scn 0.0 10.0))
(println (cons "Final Scene:" scn))
(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080))
(render cam scn 5 4 "demo-1.png")

View File

@ -1,5 +1,5 @@
use lispers::lisp::{eval, Environment};
use lispers::parser::ExpressionStream;
use lispers_core::lisp::{eval, Environment};
use lispers_core::parser::ExpressionStream;
fn main() {
let programs = [

View File

@ -1,7 +1,7 @@
use lispers::lisp::Expression;
use lispers::parser::ParserError;
use lispers_core::lisp::Expression;
use lispers_core::parser::ParserError;
use lispers::{lisp, parser};
use lispers_core::{lisp, parser};
use std::io::Write;
fn main() {

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

38
src/bin/rt_interp.rs Normal file
View File

@ -0,0 +1,38 @@
use std::env;
use lispers::raytracer::lisp::mk_raytrace;
use lispers_core::lisp::environment::EnvironmentLayer;
use lispers_core::lisp::prelude::mk_prelude;
use lispers_core::lisp::{eval, Environment};
use lispers_core::parser::ExpressionStream;
fn main() {
let program_paths: Vec<_> = env::args().skip(1).collect();
let programs: Vec<_> = program_paths
.iter()
.map(|path| std::fs::read_to_string(path).unwrap())
.collect();
let mut layer = EnvironmentLayer::new();
mk_prelude(&mut layer);
mk_raytrace(&mut layer);
let environment = Environment::from_layer(layer);
for (i, r) in
ExpressionStream::from_char_stream(programs.iter().map(|p| p.chars()).flatten()).enumerate()
{
match r {
Err(err) => {
println!("ParserError in Expression {}: {:?}", i + 1, err);
break;
}
Ok(expr) => match eval(&environment, expr) {
Ok(_) => {}
Err(e) => println!("Error evaluating Expression {}: {}", i + 1, e),
},
}
}
println!("Interpreter Done!");
}

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

@ -0,0 +1,51 @@
use lispers::raytracer::lisp::mk_raytrace;
use lispers_core::lisp::environment::EnvironmentLayer;
use lispers_core::lisp::prelude::mk_prelude;
use lispers_core::lisp::{eval, Environment};
use lispers_core::parser::ExpressionStream;
fn main() {
let programs = [
"(set 'red (material (color 1 0 0) (color 1 0 0) (color 0.5 0 0) 50 0.25))",
"(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))",
"(defun spiral-sphere (i n) (sphere (print (point (* 2 (cos (/ (* i 6.2) n))) 0.5 (* 2 (sin (/ (* i 6.2) n))))) 0.5 red))",
"(defun spiral (scn i n) (if (< i n) (scene-add (spiral scn (+ i 1) n) (spiral-sphere i n)) scn))",
"(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 'scn (spiral scn 0.0 10.0))",
"(print scn)",
"(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 @@
pub mod lisp;
pub mod parser;
pub mod raytracer;

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
}
}

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

@ -0,0 +1,282 @@
use crate::raytracer::{scene::Scene, types::Light};
use lispers_macro::{native_lisp_function, native_lisp_function_proxy};
use lispers_core::lisp::{
environment::EnvironmentLayer,
eval::{eval, EvalError},
expression::ForeignDataWrapper,
Environment, Expression,
};
use super::{
camera::Camera,
plane::{Checkerboard, Plane},
sphere::Sphere,
types::{Color, Material, Point3, RTObjectWrapper, Vector3},
};
#[native_lisp_function(eval)]
pub fn point(x: f64, y: f64, z: f64) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(Point3::new(x, y, z)))
}
#[native_lisp_function(eval)]
pub fn vector(x: f64, y: f64, z: f64) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(Vector3::new(x, y, z)))
}
#[native_lisp_function(eval)]
pub fn color(r: f64, g: f64, b: f64) -> Result<ForeignDataWrapper<Color>, EvalError> {
Ok(ForeignDataWrapper::new(Color::new(r, g, b)))
}
#[native_lisp_function(eval)]
pub fn light(
pos: ForeignDataWrapper<Point3>,
col: ForeignDataWrapper<Color>,
) -> Result<ForeignDataWrapper<Light>, EvalError> {
Ok(ForeignDataWrapper::new(Light::new(*pos, *col)))
}
#[native_lisp_function(eval)]
pub fn material(
amb: ForeignDataWrapper<Color>,
dif: ForeignDataWrapper<Color>,
spe: ForeignDataWrapper<Color>,
shi: f64,
mir: f64,
) -> Result<ForeignDataWrapper<Material>, EvalError> {
Ok(ForeignDataWrapper::new(Material::new(
*amb, *dif, *spe, shi, mir,
)))
}
#[native_lisp_function(eval)]
pub fn sphere(
pos: ForeignDataWrapper<Point3>,
rad: f64,
mat: ForeignDataWrapper<Material>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(*pos, rad, *mat))).into())
}
#[native_lisp_function(eval)]
pub fn plane(
pos: ForeignDataWrapper<Point3>,
dir: ForeignDataWrapper<Vector3>,
mat: ForeignDataWrapper<Material>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Plane::new(*pos, *dir, *mat))).into())
}
#[native_lisp_function(eval)]
pub fn checkerboard(
pos: ForeignDataWrapper<Point3>,
norm: ForeignDataWrapper<Vector3>,
mat1: ForeignDataWrapper<Material>,
mat2: ForeignDataWrapper<Material>,
sca: f64,
up: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
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())
}
#[native_lisp_function]
pub fn scene_add_object(
mut sce: ForeignDataWrapper<Scene>,
obj: ForeignDataWrapper<RTObjectWrapper>,
) -> Result<ForeignDataWrapper<Scene>, EvalError> {
sce.add_object(obj.clone());
Ok(sce)
}
#[native_lisp_function]
pub fn scene_add_light(
mut sce: ForeignDataWrapper<Scene>,
lgt: ForeignDataWrapper<Light>,
) -> Result<ForeignDataWrapper<Scene>, EvalError> {
sce.add_light(*lgt);
Ok(sce)
}
native_lisp_function_proxy!(
fname = scene_add,
eval,
dispatch = scene_add_object,
dispatch = scene_add_light
);
#[native_lisp_function(eval)]
pub fn camera(
pos: ForeignDataWrapper<Point3>,
cnt: ForeignDataWrapper<Point3>,
up: ForeignDataWrapper<Vector3>,
fovy: f64,
w: i64,
h: i64,
) -> Result<ForeignDataWrapper<Camera>, EvalError> {
Ok(ForeignDataWrapper::new(Camera::new(
*pos, *cnt, *up, fovy, w as usize, h as usize,
)))
}
#[native_lisp_function(eval)]
pub fn render(
cam: ForeignDataWrapper<Camera>,
sce: ForeignDataWrapper<Scene>,
dpt: i64,
sbp: i64,
out: String,
) -> Result<Expression, EvalError> {
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())),
}
}
#[native_lisp_function(eval)]
pub fn sin(x: f64) -> Result<f64, EvalError> {
Ok(x.sin())
}
#[native_lisp_function(eval)]
pub fn cos(x: f64) -> Result<f64, EvalError> {
Ok(x.cos())
}
#[native_lisp_function]
pub fn vadd_vv(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a + *b))
}
#[native_lisp_function]
pub fn vadd_vp(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b + *a))
}
#[native_lisp_function]
pub fn vadd_pv(
a: ForeignDataWrapper<Point3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a + *b))
}
native_lisp_function_proxy!(
fname = vadd,
eval,
dispatch = vadd_vv,
dispatch = vadd_vp,
dispatch = vadd_pv
);
#[native_lisp_function]
pub fn vsub_vv(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a - *b))
}
#[native_lisp_function]
pub fn vsub_vp(
a: ForeignDataWrapper<Vector3>,
b: ForeignDataWrapper<Point3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*b - *a))
}
#[native_lisp_function]
pub fn vsub_pv(
a: ForeignDataWrapper<Point3>,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Point3>, EvalError> {
Ok(ForeignDataWrapper::new(*a - *b))
}
native_lisp_function_proxy!(
fname = vsub,
eval,
dispatch = vsub_vv,
dispatch = vsub_vp,
dispatch = vsub_pv
);
#[native_lisp_function]
pub fn vmul_vs(
a: ForeignDataWrapper<Vector3>,
b: f64,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*a * b))
}
#[native_lisp_function]
pub fn vmul_sv(
a: f64,
b: ForeignDataWrapper<Vector3>,
) -> Result<ForeignDataWrapper<Vector3>, EvalError> {
Ok(ForeignDataWrapper::new(*b * a))
}
native_lisp_function_proxy!(fname = vmul, eval, dispatch = vmul_vs, dispatch = vmul_sv);
/// 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".to_string(), Expression::Function(scene_add));
layer.set("camera".to_string(), Expression::Function(camera));
layer.set("render".to_string(), Expression::Function(render));
layer.set("sin".to_string(), Expression::Function(sin));
layer.set("cos".to_string(), Expression::Function(cos));
layer.set("vadd".to_string(), Expression::Function(vadd));
layer.set("vsub".to_string(), Expression::Function(vsub));
layer.set("vmul".to_string(), Expression::Function(vmul));
}

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 lispers_core::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);
}