From 3c730778372df255422ae8b06870244e18ce0167 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jonas=20R=C3=B6ger?= Date: Tue, 31 Mar 2026 04:12:39 +0200 Subject: [PATCH] feat(anim): add frame-based animations --- scenes/demo-2.lisp | 94 +++++++++++++++++++++++++++++++++++++++++ src/raytracer/camera.rs | 32 ++++++++++++++ src/raytracer/lisp.rs | 92 +++++++++++++++++++++++++++++++++++++++- 3 files changed, 216 insertions(+), 2 deletions(-) create mode 100644 scenes/demo-2.lisp diff --git a/scenes/demo-2.lisp b/scenes/demo-2.lisp new file mode 100644 index 0000000..e6825c7 --- /dev/null +++ b/scenes/demo-2.lisp @@ -0,0 +1,94 @@ +(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-base (scene + (color 0.1 0.1 0.1) + '(s1 s2 p1) + '(l1 l2))) + +(set 'scn (spiral scn-base 0 10)) + +(set 'cam (camera (point 0 3 6) (point 0 0 0) (vector 0 1 0) 40 1920 1080)) + +(defun scene-fn (t) + scn) + +(defun cam-fn (t c) + (let '((pos . (point 0 4 8)) + (cnt . (point 0 0 0)) + (to . (point 1 1 1)) + (up . (vector 0 1 0)) + (fovy . 40) + (pct . (/ t 300.0))) + (let '((tpos . (vadd pos (vmul (vsub to pos) pct))) + (tfovy . (+ fovy (* 10 pct))) + ) + (camera-reposition c tpos cnt up fovy) + ) + )) + +(render-animation cam scene-fn cam-fn 300 1 4 2) diff --git a/src/raytracer/camera.rs b/src/raytracer/camera.rs index 90a0952..40f42f0 100644 --- a/src/raytracer/camera.rs +++ b/src/raytracer/camera.rs @@ -108,6 +108,38 @@ impl Camera { }); img } + + pub fn reposition( + &self, + position: Point3, + center: Point3, + up: Vector3, + fovy: Scalar, + ) -> Camera { + Camera::new(position, center, up, fovy, self.width, self.height) + } + + pub fn render_animation Scene, CFn: Fn(u32, &Camera) -> Camera>( + &self, + scene_fn: SFn, + update_cam: CFn, + frames: u32, + fps: u32, + depth: u32, + subp: u32, + ) { + let mut cam = self.to_owned(); + for t in 0..frames { + println!("Rendering frame {}/{}", t + 1, frames); + cam = update_cam(t, &cam); + let img = cam.render(&scene_fn(t), depth, subp); + + match img.save(format!("frame_{:04}.png", t)) { + Ok(_) => {} + Err(e) => print!("Could not render frame: {}", e), + } + } + } } impl Display for Camera { diff --git a/src/raytracer/lisp.rs b/src/raytracer/lisp.rs index aee6e60..4778761 100644 --- a/src/raytracer/lisp.rs +++ b/src/raytracer/lisp.rs @@ -148,6 +148,19 @@ pub fn camera( ))) } +#[native_lisp_function(eval)] +pub fn camera_reposition( + cam: ForeignDataWrapper, + pos: ForeignDataWrapper, + cnt: ForeignDataWrapper, + up: ForeignDataWrapper, + fovy: f64, +) -> Result, EvalError> { + Ok(ForeignDataWrapper::new( + cam.to_owned().reposition(*pos, *cnt, *up, fovy), + )) +} + #[native_lisp_function(eval)] pub fn render( cam: ForeignDataWrapper, @@ -165,6 +178,41 @@ pub fn render( } } +pub fn render_animation(env: &Environment, expr: Expression) -> Result { + let [cam, scene_fn, update_cam, frames, fps, depth, subp]: [Expression; 7] = expr.try_into()?; + + let cam: ForeignDataWrapper = eval(env, cam)?.try_into()?; + let frames: i64 = eval(env, frames)?.try_into()?; + let fps: i64 = eval(env, fps)?.try_into()?; + let depth: i64 = eval(env, depth)?.try_into()?; + let subp: i64 = eval(env, subp)?.try_into()?; + + let sfn = |t: u32| -> Scene { + let scene_fn_call: Expression = [scene_fn.clone(), (t as i64).into()].into(); + let scn: ForeignDataWrapper = eval(env, scene_fn_call).unwrap().try_into().unwrap(); + scn.to_owned() + }; + + let ucm = |t: u32, c: &Camera| -> Camera { + let c = ForeignDataWrapper::new(c.to_owned()); + let update_cam_call: Expression = [update_cam.clone(), (t as i64).into(), c.into()].into(); + let new_c: ForeignDataWrapper = + eval(env, update_cam_call).unwrap().try_into().unwrap(); + new_c.to_owned() + }; + + cam.render_animation( + sfn, + ucm, + frames as u32, + fps as u32, + depth as u32, + subp as u32, + ); + + Ok(Expression::Nil) +} + #[native_lisp_function(eval)] pub fn sin(x: f64) -> Result { Ok(x.sin()) @@ -231,12 +279,21 @@ pub fn vsub_pv( Ok(ForeignDataWrapper::new(*a - *b)) } +#[native_lisp_function] +pub fn vsub_pp( + a: ForeignDataWrapper, + b: ForeignDataWrapper, +) -> Result, EvalError> { + Ok(ForeignDataWrapper::new(*a - *b)) +} + native_lisp_function_proxy!( fname = vsub, eval, dispatch = vsub_vv, dispatch = vsub_vp, - dispatch = vsub_pv + dispatch = vsub_pv, + dispatch = vsub_pp ); #[native_lisp_function] @@ -255,7 +312,30 @@ pub fn vmul_sv( Ok(ForeignDataWrapper::new(*b * a)) } -native_lisp_function_proxy!(fname = vmul, eval, dispatch = vmul_vs, dispatch = vmul_sv); +#[native_lisp_function] +pub fn vmul_ps( + a: ForeignDataWrapper, + b: f64, +) -> Result, EvalError> { + Ok(ForeignDataWrapper::new(*a * b)) +} + +#[native_lisp_function] +pub fn vmul_sp( + a: f64, + b: ForeignDataWrapper, +) -> Result, EvalError> { + Ok(ForeignDataWrapper::new(*b * a)) +} + +native_lisp_function_proxy!( + fname = vmul, + eval, + dispatch = vmul_vs, + dispatch = vmul_sv, + dispatch = vmul_ps, + dispatch = vmul_sp +); /// Adds the raytracing functions to the given environment layer. pub fn mk_raytrace(layer: &mut EnvironmentLayer) { @@ -273,7 +353,15 @@ pub fn mk_raytrace(layer: &mut EnvironmentLayer) { 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( + "camera-reposition".to_string(), + Expression::Function(camera_reposition), + ); layer.set("render".to_string(), Expression::Function(render)); + layer.set( + "render-animation".to_string(), + Expression::Function(render_animation), + ); layer.set("sin".to_string(), Expression::Function(sin)); layer.set("cos".to_string(), Expression::Function(cos)); layer.set("vadd".to_string(), Expression::Function(vadd));