Compare commits

...
6 Commits
Author SHA1 Message Date
jonas 762ed3c478 closure bump 2026-09-12 14:44:08 +02:00
jonas 5c5c81325d fix(build): app name 2026-04-02 19:19:26 +02:00
jonas d1211b7157 fix(texture): display inf rec 2026-04-02 19:17:23 +02:00
jonas 99bf883eeb fix(flake): default app name 2026-04-02 18:51:15 +02:00
jonas 078ad2c401 build: install scenes properly 2026-04-02 18:17:22 +02:00
jonas 3a49460fe6 feat(sphere): add texture sphere 2026-04-02 15:10:09 +02:00
15 changed files with 400 additions and 105 deletions
+39
View File
@@ -0,0 +1,39 @@
use std::path::Path;
fn copy_dir(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> std::io::Result<()> {
std::fs::create_dir_all(&dst)?;
for entry in std::fs::read_dir(src)? {
let entry = entry?;
if entry.file_type()?.is_dir() {
copy_dir(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
std::fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
fn main() {
let use_local_scenes = std::env::var("LISPERS_USE_LOCAL_SCENES").unwrap_or_default() == "1";
let no_copy = std::env::var("LISPERS_DONT_COPY_SCENES").unwrap_or_default() == "1";
let out_dir = match std::env::var("LISPERS_OUT_DIR") {
Ok(val) => val,
Err(_) => std::env::var("OUT_DIR").unwrap(),
};
let mut scenes_dir = Path::new(&std::env::var("CARGO_MANIFEST_DIR").unwrap())
.canonicalize()
.unwrap()
.join("scenes");
if !use_local_scenes {
let tgt_scenes_dir = Path::new(&out_dir).join("scenes");
if !no_copy {
copy_dir(&scenes_dir, &tgt_scenes_dir).expect("Failed to copy scenes directory");
}
scenes_dir = tgt_scenes_dir;
}
println!("cargo:rustc-env=SCENES_DIR={}", scenes_dir.display());
}
+14 -4
View File
@@ -79,7 +79,15 @@
overlays = [rust-overlay.overlays.default]; overlays = [rust-overlay.overlays.default];
}; };
packages.lispers = cargoNix.workspaceMembers.lispers.build; packages.lispers = cargoNix.workspaceMembers.lispers.build.overrideAttrs (attrs: {
preConfigure = ''
export LISPERS_OUT_DIR="$out"
export LISPERS_DONT_COPY_SCENES=1
'';
postInstall = ''
cp -r $src/scenes $out/scenes
'';
});
packages.default = self'.packages.lispers; packages.default = self'.packages.lispers;
apps = { apps = {
lisp_demo = { lisp_demo = {
@@ -94,7 +102,7 @@
type = "app"; type = "app";
program = "${self'.packages.lispers}/bin/rt_demo"; program = "${self'.packages.lispers}/bin/rt_demo";
}; };
rt_demo_lisp = { rt_lisp_demo = {
type = "app"; type = "app";
program = "${self'.packages.lispers}/bin/rt_lisp_demo"; program = "${self'.packages.lispers}/bin/rt_lisp_demo";
}; };
@@ -102,11 +110,13 @@
type = "app"; type = "app";
program = "${self'.packages.lispers}/bin/rt_interp"; program = "${self'.packages.lispers}/bin/rt_interp";
}; };
default = self'.apps.rt_demo_lisp; default = self'.apps.rt_lisp_demo;
}; };
devShells.default = pkgs.mkShell { devShells.default = pkgs.mkShell {
inputsFrom = [self'.packages.lispers]; shellHook = ''
export LISPERS_USE_LOCAL_SCENES=1
'';
LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib"; LIBCLANG_PATH = "${pkgs.llvmPackages.libclang.lib}/lib";
nativeBuildInputs = [rust-toolchain pkgs.pkg-config pkgs.ffmpeg_4]; nativeBuildInputs = [rust-toolchain pkgs.pkg-config pkgs.ffmpeg_4];
BINDGEN_EXTRA_CLANG_ARGS = [ BINDGEN_EXTRA_CLANG_ARGS = [
+25 -1
View File
@@ -1,5 +1,5 @@
use super::{expression::Expression, prelude::mk_prelude}; use super::{expression::Expression, prelude::mk_prelude};
use std::{cell::RefCell, collections::HashMap, rc::Rc}; use std::{cell::RefCell, collections::HashMap, fmt::Display, rc::Rc};
#[derive(PartialEq, Clone, Debug)] #[derive(PartialEq, Clone, Debug)]
/// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from /// A Environment is a stack of `EnvironmentLayer`s. Each `EnvironmentLayer` is a mapping from
@@ -136,6 +136,30 @@ impl Default for Environment<'_> {
} }
} }
impl Display for Environment<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if let Some(x) = &self.outer {
write!(f, "{}<{}", self.layer, x)
} else {
write!(f, "")
}
}
}
impl Display for EnvironmentLayer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"{{{}}}",
self.symbols
.iter()
.map(|(k, v)| format!("{}={}", k, v))
.collect::<Vec<_>>()
.join(", ")
)
}
}
#[test] #[test]
fn test_environment() { fn test_environment() {
let mut env = Environment::new(); let mut env = Environment::new();
+13 -1
View File
@@ -97,12 +97,23 @@ fn dispatch_anonymous_function(
} }
for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) { for (arg, symbol) in args.iter_mut().zip(argument_symbols.iter()) {
overlay.set(symbol.to_owned(), eval(env, arg.to_owned())?); overlay.set(symbol.to_owned(), eval(&env, arg.to_owned())?);
} }
eval(&env.overlay(overlay), body) eval(&env.overlay(overlay), body)
} }
/// Dispatch a closure. Evaluates `body` in `env` overlayed with the `layer`
fn dispatch_closure(
env: &Environment,
layer: EnvironmentLayer,
body: Expression,
) -> Result<Expression, EvalError> {
println!("Dispatching closure: {} -> {}", &layer, &body);
let env = env.overlay(layer);
eval(&env, body)
}
/// Evaluate an expression inside an environment /// Evaluate an expression inside an environment
pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
match expr { match expr {
@@ -114,6 +125,7 @@ pub fn eval(env: &Environment, expr: Expression) -> Result<Expression, EvalError
} => dispatch_anonymous_function(env, argument_symbols, *body, *rhs), } => dispatch_anonymous_function(env, argument_symbols, *body, *rhs),
a => Err(EvalError::NotAFunction(a)), a => Err(EvalError::NotAFunction(a)),
}, },
Expression::Closure { layer, body } => dispatch_closure(env, layer, *body),
Expression::Quote(e) => Ok(*e), Expression::Quote(e) => Ok(*e),
Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)), Expression::Symbol(s) => env.get(&s).ok_or(EvalError::SymbolNotBound(s)),
x => Ok(x), x => Ok(x),
+12
View File
@@ -7,6 +7,7 @@ use std::ops::DerefMut;
use as_any::AsAny; use as_any::AsAny;
use super::environment::Environment; use super::environment::Environment;
use super::environment::EnvironmentLayer;
use super::eval::CellIterator; use super::eval::CellIterator;
use super::eval::EvalError; use super::eval::EvalError;
@@ -128,6 +129,10 @@ pub enum Expression {
Cell(Box<Expression>, Box<Expression>), Cell(Box<Expression>, Box<Expression>),
/// A function expression pointing to native code. /// A function expression pointing to native code.
Function(fn(&Environment, Expression) -> Result<Expression, EvalError>), Function(fn(&Environment, Expression) -> Result<Expression, EvalError>),
Closure {
layer: EnvironmentLayer,
body: Box<Expression>,
},
/// A anonymous function expression consisting of bound symbols and a body expression. /// A anonymous function expression consisting of bound symbols and a body expression.
AnonymousFunction { AnonymousFunction {
argument_symbols: Vec<String>, argument_symbols: Vec<String>,
@@ -169,7 +174,9 @@ impl PartialEq for Expression {
(ForeignExpression(f1), ForeignExpression(f2)) => PartialEq::eq(f1, f2), (ForeignExpression(f1), ForeignExpression(f2)) => PartialEq::eq(f1, f2),
(Quote(e1), Quote(e2)) => PartialEq::eq(e1, e2), (Quote(e1), Quote(e2)) => PartialEq::eq(e1, e2),
(Symbol(s1), Symbol(s2)) => PartialEq::eq(s1, s2), (Symbol(s1), Symbol(s2)) => PartialEq::eq(s1, s2),
(Integer(i1), Integer(i2)) => PartialEq::eq(i1, i2),
(Float(f1), Float(f2)) => PartialEq::eq(f1, f2), (Float(f1), Float(f2)) => PartialEq::eq(f1, f2),
(String(s1), String(s2)) => PartialEq::eq(s1, s2),
(Nil, Nil) => true, (Nil, Nil) => true,
(True, True) => true, (True, True) => true,
_ => false, _ => false,
@@ -198,6 +205,8 @@ impl PartialOrd for Expression {
(Quote(e1), Quote(e2)) => e1.partial_cmp(e2), (Quote(e1), Quote(e2)) => e1.partial_cmp(e2),
(Symbol(s1), Symbol(s2)) => s1.partial_cmp(s2), (Symbol(s1), Symbol(s2)) => s1.partial_cmp(s2),
(Float(f1), Float(f2)) => f1.partial_cmp(f2), (Float(f1), Float(f2)) => f1.partial_cmp(f2),
(Integer(i1), Integer(i2)) => i1.partial_cmp(i2),
(String(s1), String(s2)) => s1.partial_cmp(s2),
(Nil, Nil) => Some(std::cmp::Ordering::Equal), (Nil, Nil) => Some(std::cmp::Ordering::Equal),
(True, True) => Some(std::cmp::Ordering::Equal), (True, True) => Some(std::cmp::Ordering::Equal),
_ => None, _ => None,
@@ -407,6 +416,9 @@ impl Display for Expression {
} }
} }
Expression::Function(_) => write!(f, "<function>"), Expression::Function(_) => write!(f, "<function>"),
Expression::Closure { layer, body } => {
write!(f, "(closure captured={}, body={})", layer, body)
}
Expression::AnonymousFunction { Expression::AnonymousFunction {
argument_symbols, argument_symbols,
body, body,
+14 -3
View File
@@ -148,13 +148,18 @@ pub fn prelude_let(env: &Environment, expr: Expression) -> Result<Expression, Ev
}) })
.collect::<Result<HashMap<String, Expression>, EvalError>>()?; .collect::<Result<HashMap<String, Expression>, EvalError>>()?;
eval(&env.overlay(bindings.into()), body) Ok(Expression::Closure {
layer: bindings.into(),
body: Box::new(body),
})
} }
pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> { pub fn prelude_if(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
let [predicate, e_then, e_else] = expr.try_into()?; let [predicate, e_then, e_else]: [Expression; 3] = expr.try_into()?;
match eval(env, predicate)? { let val = eval(env, predicate.clone())?;
match val {
Expression::Nil => eval(env, e_else), Expression::Nil => eval(env, e_else),
_ => eval(env, e_then), _ => eval(env, e_then),
} }
@@ -348,18 +353,24 @@ pub fn prelude_include(env: &Environment, expr: Expression) -> Result<Expression
let [expr] = expr.try_into()?; let [expr] = expr.try_into()?;
let lisp_file: String = eval(env, expr)?.try_into()?; let lisp_file: String = eval(env, expr)?.try_into()?;
println!("From File: {}", env.get("FILE").unwrap());
// Try to resolve as relative to FILE // Try to resolve as relative to FILE
let resolved_lisp_file: PathBuf = PathBuf::from( let resolved_lisp_file: PathBuf = PathBuf::from(
env.get("FILE") env.get("FILE")
.map(|x| x.try_into()) .map(|x| x.try_into())
.unwrap_or(Ok(String::new()))?, .unwrap_or(Ok(String::new()))?,
) )
.canonicalize()
.map_err(|x| EvalError::RuntimeError(x.to_string()))?
.parent() .parent()
.ok_or(EvalError::RuntimeError( .ok_or(EvalError::RuntimeError(
"Could not get parent of current file.".to_string(), "Could not get parent of current file.".to_string(),
))? ))?
.join(&lisp_file); .join(&lisp_file);
println!("Loading {}", resolved_lisp_file.display());
let lisp_string = std::fs::read_to_string(&resolved_lisp_file) let lisp_string = std::fs::read_to_string(&resolved_lisp_file)
.map_err(|e| EvalError::RuntimeError(e.to_string()))?; .map_err(|e| EvalError::RuntimeError(e.to_string()))?;
+2
View File
@@ -11,6 +11,7 @@ pub enum ParserError {
UnexpectedToken(Token), UnexpectedToken(Token),
TokenizerError(TokenizerError), TokenizerError(TokenizerError),
UnexpectedEndOfInput, UnexpectedEndOfInput,
RuntimeError(String),
} }
impl From<TokenizerError> for ParserError { impl From<TokenizerError> for ParserError {
@@ -25,6 +26,7 @@ impl Display for ParserError {
ParserError::TokenizerError(t) => write!(f, "Tokenizer Error: {}", t), ParserError::TokenizerError(t) => write!(f, "Tokenizer Error: {}", t),
ParserError::UnexpectedToken(t) => write!(f, "Unexpecte Token: {}", t), ParserError::UnexpectedToken(t) => write!(f, "Unexpecte Token: {}", t),
ParserError::UnexpectedEndOfInput => write!(f, "Unexpected end of input."), ParserError::UnexpectedEndOfInput => write!(f, "Unexpected end of input."),
ParserError::RuntimeError(s) => write!(f, "{}", s),
} }
} }
} }
+20 -27
View File
@@ -17,32 +17,7 @@
(color 0 0.6 0) (color 0 0.6 0)
50 0.25)) 50 0.25))
(set 's1 (set 'mandelbrot-red
(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 'mandelbrot
(mandelbrot-texture (mandelbrot-texture
1800.0 1800.0
(point2 -0.7489967346191402 -0.06952285766601607) (point2 -0.7489967346191402 -0.06952285766601607)
@@ -52,9 +27,27 @@
(color 0.3 0 0) (color 0.3 0 0)
)) ))
(set 'mandelbrot-blue
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0 0 0.3)
(color 0 0 0.3)
(color 0 0 0.3)
))
(set 's1
(sphere
(point 0 1 0) 1 blue))
(set 's2
(sphere
(point 2 0.5 2) 0.5 green))
(set 'p1 (set 'p1
(texture-plane (texture-plane
mandelbrot mandelbrot-red
(point 0 0 0) (point 0 0 0)
(vector 0 1 0) (vector 0 1 0)
1.0 1.0
+47
View File
@@ -0,0 +1,47 @@
(include "./materials.lisp")
(set 'frames 300)
(set 'fps 30)
(set 'ray-depth 5)
(set 'sub-pixel 2)
(set 'mz (mandel-zoom frames 1800 5000))
(defun mandel-plane (t)
(texture-plane
(mz t)
(point 0 0 0)
(vector 0 1 0)
1.0
(vector 1 0 0)
))
(set 'l1 (light (point 3 10 5) (color 1 1 1)))
(set 'l2 (light (point 2 10 5) (color 1 1 1)))
(defun scn (t)
(scene
(color 0.1 0.1 0.1)
'((mandel-plane t))
'(l1 l2)
))
(defun cam (t c) c)
(set 'base-cam
(camera
(point 0 3 6)
(point 0 0 0)
(vector 0 1 0)
40 1920 1080))
(render-animation
base-cam
"demo-4-animation.mp4"
scn
cam
frames
fps
ray-depth
sub-pixel)
+29
View File
@@ -35,3 +35,32 @@
(color 0.01 0.05 0.15) (color 0.01 0.05 0.15)
(color 0.01 0.05 0.15) (color 0.01 0.05 0.15)
20 0.7)) 20 0.7))
(set 'mandelbrot-red
(mandelbrot-texture
1800.0
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
))
(defun mandel-zoom (n z1 z2)
(let '((n . n)
(z1 . z1)
(z2 . z2))
(lambda (t)
(let '((pct . (/ t (* n 1.0)))
(range . (- z2 z1)))
(mandelbrot-texture
(+ z1 (* pct range))
(point2 -0.7489967346191402 -0.06952285766601607)
1000
(color 0.3 0 0)
(color 0.3 0 0)
(color 0.3 0 0)
)
)))
)
+10 -2
View File
@@ -8,15 +8,20 @@ fn main() {
"(print myvar) (print 'myvar)", "(print myvar) (print 'myvar)",
"(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)", "(car (cons 'a 'b)) (cdr (cons 'c 'd)) (cons 'a 'b)",
"(eval (car (cons 'myvar 'b)))", "(eval (car (cons 'myvar 'b)))",
"(defun test (i) (progn (println i) (* 2 i)))",
"(test 10)",
"(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))", "(set 'pow (lambda (a b) (if (= b 0) 1 (* a (pow a (- b 1))))))",
"pow", "pow",
"(pow 2 10)", "(pow 2 10)",
"(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))", "(let '((fib . (lambda (n) (if (< n 2) n (+ (fib (- n 1)) (fib (- n 2))))))) (fib 10))",
"(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))", "(defun do-n-times (f n) (if (= n 0) '() (cons (f) (do-n-times f (- n 1)))))",
"(do-n-times (lambda () (print 'hello)) 5)", "(do-n-times (lambda () (println 'hello)) 5)",
"(progn (print 'hello) (print 'world))", "(progn (print 'hello) (print 'world))",
"(load \"(defun loaded-foo (x) (+ x 1))\")", "(load \"(defun loaded-foo (x) (+ x 1))\")",
"(loaded-foo 1)", "(loaded-foo 1)",
"(defun bind-test (x) (let '((x . x)) (lambda (y) (+ x y))))",
"(set 'add10 (bind-test 10))",
"(add10 10)",
]; ];
let environment = Environment::default(); let environment = Environment::default();
@@ -31,7 +36,10 @@ fn main() {
println!("Evaluating: {}", expr.clone()); println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) { match eval(&environment, expr) {
Ok(e) => println!("=> {}", e), Ok(e) => println!("=> {}", e),
Err(e) => println!("Error: {}", e), Err(e) => {
println!("Error: {}", e);
return;
}
} }
} }
} }
+51 -24
View File
@@ -1,47 +1,74 @@
use std::collections::HashMap;
use std::path::Path;
use lispers::raytracer::lisp::mk_raytrace; use lispers::raytracer::lisp::mk_raytrace;
use lispers_core::lisp::environment::EnvironmentLayer; use lispers_core::lisp::environment::EnvironmentLayer;
use lispers_core::lisp::prelude::mk_prelude; use lispers_core::lisp::prelude::mk_prelude;
use lispers_core::lisp::{eval, Environment}; use lispers_core::lisp::{eval, Environment};
use lispers_core::parser::ExpressionStream; use lispers_core::parser::ExpressionStream;
const SCENES_DIR: &str = env!("SCENES_DIR");
fn main() { fn main() {
let programs = [ println!("Loading scenes from directory: {}", SCENES_DIR);
"(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))", let mut scenes = HashMap::new();
"(set 'green (material (color 0 1 0) (color 0 1 0) (color 0 0.6 0) 50 0.25))", for e in std::fs::read_dir(Path::new(SCENES_DIR)).expect("Failed to read scenes directory") {
"(set 'white (material (color 1 1 1) (color 1 1 1) (color 0.6 0.6 0.6) 100 0.5))", let e = e.expect("Failed to read scene file");
"(set 'black (material (color 0 0 0) (color 0 0 0) (color 0.6 0.6 0.6) 100 0.5))", let t = e.file_type().expect("Failed to read scene file type");
"(set 's1 (sphere (point 0 1 0) 1 blue))", let n = e
"(set 's2 (sphere (point 2 0.5 2) 0.5 green))", .file_name()
"(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))", .into_string()
"(defun spiral (scn i n) (if (< i n) (scene-add (spiral scn (+ i 1) n) (spiral-sphere i n)) scn))", .expect("Failed to read scene file name");
"(set 'p1 (checkerboard (point 0 0 0) (vector 0 1 0) black white 0.5 (vector 0.5 0 1)))", if t.is_file() && n.starts_with("demo-") && n.ends_with(".lisp") {
"(set 'l1 (light (point 3 10 5) (color 1 1 1)))", scenes.insert(n, e);
"(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)", let args: Vec<_> = std::env::args().collect();
"(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\")", if args.len() != 2 {
]; println!("Usage: {} <scene-file.lisp>", args[0]);
println!("Available scene files:");
for name in scenes.keys() {
println!(" {}", name);
}
return;
}
let mut layer = EnvironmentLayer::new(); let mut layer = EnvironmentLayer::new();
mk_prelude(&mut layer); mk_prelude(&mut layer);
mk_raytrace(&mut layer); mk_raytrace(&mut layer);
let mut environment = Environment::from_layer(layer);
environment.set(
"FILE".to_string(),
scenes
.get(&args[1])
.expect("Scene file not found")
.path()
.display()
.to_string()
.into(),
);
let environment = Environment::from_layer(layer); for r in ExpressionStream::from_char_stream(
std::fs::read_to_string(scenes.get(&args[1]).expect("Scene file not found").path())
for r in ExpressionStream::from_char_stream(programs.iter().map(|p| p.chars()).flatten()) { .expect("Failed to read scene file")
.chars(),
) {
match r { match r {
Err(err) => { Err(err) => {
println!("ParserError: {:?}", err); println!("ParserError: {:?}", err);
break; return;
} }
Ok(expr) => { Ok(expr) => {
println!("Evaluating: {}", expr.clone()); println!("Evaluating: {}", expr.clone());
match eval(&environment, expr) { match eval(&environment, expr) {
Ok(e) => println!("=> {}", e), Ok(e) => println!("=> {}", e),
Err(e) => println!("Error: {}", e), Err(e) => {
println!("Error: {}", e);
return;
}
} }
} }
} }
+21
View File
@@ -2,6 +2,7 @@ use std::path::PathBuf;
use crate::raytracer::{ use crate::raytracer::{
scene::Scene, scene::Scene,
sphere::TextureSphere,
texture::TextureWrapper, texture::TextureWrapper,
types::{Light, Point2}, types::{Light, Point2},
}; };
@@ -74,6 +75,22 @@ pub fn sphere(
Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(*pos, rad, *mat))).into()) Ok(ForeignDataWrapper::new(RTObjectWrapper::from(Sphere::new(*pos, rad, *mat))).into())
} }
#[native_lisp_function(eval)]
pub fn texture_sphere(
pos: ForeignDataWrapper<Point3>,
rad: f64,
tex: ForeignDataWrapper<TextureWrapper>,
) -> Result<ForeignDataWrapper<RTObjectWrapper>, EvalError> {
Ok(
ForeignDataWrapper::new(RTObjectWrapper::from(TextureSphere::new(
*pos,
rad,
tex.clone(),
)))
.into(),
)
}
#[native_lisp_function(eval)] #[native_lisp_function(eval)]
pub fn plane( pub fn plane(
pos: ForeignDataWrapper<Point3>, pos: ForeignDataWrapper<Point3>,
@@ -539,6 +556,10 @@ pub fn mk_raytrace(layer: &mut EnvironmentLayer) {
Expression::Function(mandelbrot_texture), Expression::Function(mandelbrot_texture),
); );
layer.set("sphere".to_string(), Expression::Function(sphere)); layer.set("sphere".to_string(), Expression::Function(sphere));
layer.set(
"texture-sphere".to_string(),
Expression::Function(texture_sphere),
);
layer.set("scene".to_string(), Expression::Function(scene)); layer.set("scene".to_string(), Expression::Function(scene));
layer.set("scene-add".to_string(), Expression::Function(scene_add)); layer.set("scene-add".to_string(), Expression::Function(scene_add));
layer.set("camera".to_string(), Expression::Function(camera)); layer.set("camera".to_string(), Expression::Function(camera));
+71 -17
View File
@@ -1,4 +1,7 @@
use super::types::{Intersect, Material, Point3, Ray, Scalar, Vector3}; use super::{
texture::TextureWrapper,
types::{Intersect, Material, Point2, Point3, Ray, Scalar, Vector3},
};
extern crate nalgebra as na; extern crate nalgebra as na;
@@ -13,6 +16,17 @@ pub struct Sphere {
material: Material, material: Material,
} }
/// A sphere in 3D space
#[derive(PartialEq, Clone, Debug)]
pub struct TextureSphere {
/// Center of the sphere
center: Point3,
/// Radius of the sphere
radius: Scalar,
/// texture of the sphere
texture: TextureWrapper,
}
impl Sphere { impl Sphere {
/// Create a new sphere at `center` with `radius` and `material`. /// Create a new sphere at `center` with `radius` and `material`.
pub fn new(center: Point3, radius: Scalar, material: Material) -> Sphere { pub fn new(center: Point3, radius: Scalar, material: Material) -> Sphere {
@@ -27,13 +41,12 @@ impl Sphere {
/// Numerical error tolerance /// Numerical error tolerance
const EPSILON: Scalar = 1e-5; const EPSILON: Scalar = 1e-5;
impl Intersect for Sphere { fn intersect(ray: &Ray, center: &Point3, radius: Scalar) -> Option<(Point3, Vector3, Scalar)> {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> { let co = ray.origin - center;
let co = ray.origin - self.center;
let a = ray.direction.dot(&ray.direction); let a = ray.direction.dot(&ray.direction);
let b = 2.0 * ray.direction.dot(&co); let b = 2.0 * ray.direction.dot(&co);
let c = co.dot(&co) - (self.radius * self.radius); let c = co.dot(&co) - (radius * radius);
let d = b * b - 4.0 * a * c; let d = b * b - 4.0 * a * c;
if d >= 0.0 { if d >= 0.0 {
@@ -53,25 +66,23 @@ impl Intersect for Sphere {
let isect_pt: Point3 = ray.origin + ray.direction * t; let isect_pt: Point3 = ray.origin + ray.direction * t;
if c >= 0.0 { if c >= 0.0 {
return Some(( return Some((isect_pt, (isect_pt - center) / radius, t));
isect_pt,
(isect_pt - self.center) / self.radius,
t,
self.material.clone(),
));
} else { } else {
return Some(( return Some((isect_pt, -(isect_pt - center) / radius, t));
isect_pt,
-(isect_pt - self.center) / self.radius,
t,
self.material.clone(),
));
} }
} }
} }
None None
} }
impl Intersect for Sphere {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> {
match intersect(ray, &self.center, self.radius) {
Some((isect_pt, normal, t)) => Some((isect_pt, normal, t, self.material.clone())),
None => None,
}
}
} }
impl std::fmt::Display for Sphere { impl std::fmt::Display for Sphere {
@@ -89,3 +100,46 @@ impl PartialOrd for Sphere {
None None
} }
} }
impl TextureSphere {
/// Create a new sphere at `center` with `radius` and `texture`.
pub fn new(center: Point3, radius: Scalar, texture: TextureWrapper) -> TextureSphere {
TextureSphere {
center,
radius,
texture,
}
}
}
impl Intersect for TextureSphere {
fn intersect(&self, ray: &Ray) -> Option<(Point3, Vector3, Scalar, Material)> {
match intersect(ray, &self.center, self.radius) {
Some((isect_pt, normal, t)) => {
let n_isect_pt = (isect_pt - self.center) / self.radius;
let uv: Point2 = Point2::new(
0.5 + (n_isect_pt.z.atan2(n_isect_pt.x) / (2.0 * std::f64::consts::PI)),
0.5 - (n_isect_pt.y).asin() / std::f64::consts::PI,
);
Some((isect_pt, normal, t, self.texture.material_at(uv)))
}
None => None,
}
}
}
impl std::fmt::Display for TextureSphere {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(
f,
"(sphere center: {}, radius: {}, texture: {})",
self.center, self.radius, self.texture
)
}
}
impl PartialOrd for TextureSphere {
fn partial_cmp(&self, _other: &Self) -> Option<std::cmp::Ordering> {
None
}
}
+9 -3
View File
@@ -14,7 +14,7 @@ pub trait Texture: Display + Debug + AsAny + Sync + Send {
fn material_at(&self, pt: Point2) -> Material; fn material_at(&self, pt: Point2) -> Material;
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct TextureWrapper(Arc<dyn Texture>); pub struct TextureWrapper(Arc<dyn Texture>);
impl TextureWrapper { impl TextureWrapper {
@@ -25,7 +25,13 @@ impl TextureWrapper {
impl Display for TextureWrapper { impl Display for TextureWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Display::fmt(&self, f) Display::fmt(&self.0, f)
}
}
impl Debug for TextureWrapper {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Debug::fmt(&self.0, f)
} }
} }
@@ -114,7 +120,7 @@ impl Debug for MandelbrotTexture {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!( write!(
f, f,
"MandelbrotTexture{{at={}, max_iter={}}}", "MandelbrotTexture{{at={:?}, max_iter={:?}}}",
self.at, self.max_iter self.at, self.max_iter
) )
} }