feat(camera): render anim using ffmpeg
This commit is contained in:
@@ -1,11 +1,15 @@
|
||||
use std::fmt::Display;
|
||||
use std::{fmt::Display, path::Path};
|
||||
|
||||
use super::{
|
||||
scene::Scene,
|
||||
types::{Color, Point3, Ray, Scalar, Vector3},
|
||||
RTError,
|
||||
};
|
||||
use image::RgbImage;
|
||||
use lispers_core::lisp::eval::EvalError;
|
||||
use ndarray::Array3;
|
||||
use rayon::prelude::*;
|
||||
use video_rs::{encode::Settings, Encoder, Time};
|
||||
|
||||
/// A camera that can render a scene.
|
||||
#[derive(Clone, PartialEq, Debug)]
|
||||
@@ -119,26 +123,47 @@ impl Camera {
|
||||
Camera::new(position, center, up, fovy, self.width, self.height)
|
||||
}
|
||||
|
||||
pub fn render_animation<SFn: Fn(u32) -> Scene, CFn: Fn(u32, &Camera) -> Camera>(
|
||||
pub fn render_animation<
|
||||
SFn: Fn(u32) -> Result<Scene, EvalError>,
|
||||
CFn: Fn(u32, &Camera) -> Result<Camera, EvalError>,
|
||||
>(
|
||||
&self,
|
||||
path: &Path,
|
||||
scene_fn: SFn,
|
||||
update_cam: CFn,
|
||||
frames: u32,
|
||||
fps: u32,
|
||||
depth: u32,
|
||||
subp: u32,
|
||||
) {
|
||||
) -> Result<(), RTError> {
|
||||
let mut encoder = Encoder::new(
|
||||
path,
|
||||
Settings::preset_h264_yuv420p(self.width, self.height, false),
|
||||
)?;
|
||||
let frame_duration = Time::from_nth_of_a_second(fps as usize);
|
||||
let mut timestamp = Time::zero();
|
||||
|
||||
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);
|
||||
println!(
|
||||
"Rendering frame {}/{} for {}",
|
||||
t + 1,
|
||||
frames,
|
||||
path.display()
|
||||
);
|
||||
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),
|
||||
}
|
||||
let frame = Array3::from_shape_fn((self.height, self.width, 3), |(y, x, c)| {
|
||||
img.get_pixel(x as u32, y as u32)[c]
|
||||
});
|
||||
|
||||
encoder.encode(&frame, timestamp)?;
|
||||
timestamp = timestamp.aligned_with(frame_duration).add();
|
||||
}
|
||||
|
||||
encoder.finish()?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::raytracer::{scene::Scene, types::Light};
|
||||
|
||||
use lispers_macro::{native_lisp_function, native_lisp_function_proxy};
|
||||
@@ -14,6 +16,7 @@ use super::{
|
||||
plane::{Checkerboard, Plane},
|
||||
sphere::Sphere,
|
||||
types::{Color, Material, Point3, RTObjectWrapper, Vector3},
|
||||
RTError,
|
||||
};
|
||||
|
||||
#[native_lisp_function(eval)]
|
||||
@@ -179,38 +182,44 @@ pub fn render(
|
||||
}
|
||||
|
||||
pub fn render_animation(env: &Environment, expr: Expression) -> Result<Expression, EvalError> {
|
||||
let [cam, scene_fn, update_cam, frames, fps, depth, subp]: [Expression; 7] = expr.try_into()?;
|
||||
let [cam, path, scene_fn, update_cam, frames, fps, depth, subp]: [Expression; 8] =
|
||||
expr.try_into()?;
|
||||
|
||||
let cam: ForeignDataWrapper<Camera> = eval(env, cam)?.try_into()?;
|
||||
let path: String = eval(env, path)?.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 sfn = |t: u32| -> Result<Scene, EvalError> {
|
||||
let scene_fn_call: Expression = [scene_fn.clone(), (t as i64).into()].into();
|
||||
let scn: ForeignDataWrapper<Scene> = eval(env, scene_fn_call).unwrap().try_into().unwrap();
|
||||
scn.to_owned()
|
||||
let scn: ForeignDataWrapper<Scene> = eval(env, scene_fn_call)?.try_into()?;
|
||||
Ok(scn.to_owned())
|
||||
};
|
||||
|
||||
let ucm = |t: u32, c: &Camera| -> Camera {
|
||||
let ucm = |t: u32, c: &Camera| -> Result<Camera, EvalError> {
|
||||
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<Camera> =
|
||||
eval(env, update_cam_call).unwrap().try_into().unwrap();
|
||||
new_c.to_owned()
|
||||
let new_c: ForeignDataWrapper<Camera> = eval(env, update_cam_call)?.try_into()?;
|
||||
Ok(new_c.to_owned())
|
||||
};
|
||||
|
||||
cam.render_animation(
|
||||
let path: PathBuf = path.into();
|
||||
|
||||
match cam.render_animation(
|
||||
&path,
|
||||
sfn,
|
||||
ucm,
|
||||
frames as u32,
|
||||
fps as u32,
|
||||
depth as u32,
|
||||
subp as u32,
|
||||
);
|
||||
|
||||
Ok(Expression::Nil)
|
||||
) {
|
||||
Ok(()) => Ok(Expression::Nil),
|
||||
Err(RTError::EvalError(e)) => Err(e),
|
||||
Err(RTError::FFMpegError(e)) => Err(EvalError::RuntimeError(e.to_string())),
|
||||
}
|
||||
}
|
||||
|
||||
#[native_lisp_function(eval)]
|
||||
|
||||
@@ -5,3 +5,21 @@ pub mod scene;
|
||||
pub mod sphere;
|
||||
pub mod types;
|
||||
mod vec;
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RTError {
|
||||
EvalError(lispers_core::lisp::eval::EvalError),
|
||||
FFMpegError(video_rs::Error),
|
||||
}
|
||||
|
||||
impl From<lispers_core::lisp::eval::EvalError> for RTError {
|
||||
fn from(value: lispers_core::lisp::eval::EvalError) -> Self {
|
||||
RTError::EvalError(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<video_rs::Error> for RTError {
|
||||
fn from(value: video_rs::Error) -> Self {
|
||||
RTError::FFMpegError(value)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user