use image::{ codecs::hdr::{HdrDecoder, HdrMetadata}, Rgb, }; use rand::{Rng, SeedableRng}; use std::fs::File; use std::io::BufReader; use std::process::Command; use std::sync::Arc; use rpt::*; fn rgb_to_color(rgb: Rgb) -> Color { glm::vec3(rgb.0[0] as f64, rgb.0[1] as f64, rgb.0[2] as f64) } fn load_hdr(url: &str) -> color_eyre::Result { let reader = ureq::get(url).call()?.into_reader(); let decoder = HdrDecoder::new(BufReader::new(reader))?; let HdrMetadata { width, height, .. } = decoder.metadata(); let pix = decoder.read_image_hdr()?; Ok(Hdri::new( width, height, pix.into_iter().map(rgb_to_color).collect(), )) } const TEST: bool = false; fn main() -> color_eyre::Result<()> { color_eyre::install()?; std::fs::create_dir_all("video")?; const N: usize = 25; let mut rng = rand::rngs::StdRng::seed_from_u64(123); let pos = (0..N) .map(|i| { glm::vec3( (i / 5) as f64 / 5. - 0.375, rng.gen_range(4.0..6.0), (i % 5) as f64 / 5. - 0.375, ) }) .collect(); let mut cur_state = ParticleState { pos, vel: vec![glm::vec3(0., 0., 0.); N], }; const R: f64 = 0.15; let system = MarblesSystem { radius: R }; let hdri = load_hdr("https://hdrihaven.com/files/hdris/ballroom_8k.hdr")?; let surface_shape = Arc::new(load_obj(File::open("examples/monomial.obj")?)?.scale(&glm::vec3(1., 1., 1.))); for frame in 0..180 { let mut scene = Scene::new(); if !TEST { scene.environment = Environment::Hdri(hdri.clone()); scene.add(Light::Object( Object::new( sphere() .scale(&glm::vec3(1.5, 1.5, 1.5)) .translate(&glm::vec3(0.0, 5.0, 0.0)), ) .material(Material::light(hex_color(0xFFFFFF), 15.0)), )); } else { scene.add(Light::Ambient(glm::vec3(0.01, 0.01, 0.01))); } let glass = Material::clear(1.5, 0.0001); scene.add(Object::new(surface_shape.clone()).material(glass)); let colors = [0x264653, 0x2A9D8F, 0xE9C46A, 0xF4A261, 0xE76F51]; let surf = monomial_surface(2., 4.); for i in 0..N { let mut pos = cur_state.pos[i]; let closest = surf.closest_point_precise(&pos); let vec = pos - closest; if glm::length(&vec) < R * 1.05 { pos = closest + glm::normalize(&vec) * R * 1.05; } pos.y = pos.y.max(R - 0.06); scene.add( Object::new(sphere().scale(&glm::vec3(R, R, R)).translate(&pos)) .material(Material::specular(hex_color(colors[i % colors.len()]), 0.1)), ); } scene.add( Object::new(polygon(&[ glm::vec3(20.0, -0.06, 20.0), glm::vec3(20.0, -0.06, -20.0), glm::vec3(-20.0, -0.06, -20.0), glm::vec3(-20.0, -0.06, 20.0), ])) .material(Material::diffuse(hex_color(0xAAAAAA))), ); let camera = Camera::look_at( glm::vec3(0.0, 1.0, 6.0), glm::vec3(0.0, 1.0, 0.0), glm::vec3(0.0, 1.0, 0.0), std::f64::consts::FRAC_PI_4, ) .focus(glm::vec3(0.0, 1.0, 0.0), 0.02); if TEST { Renderer::new(&scene, camera) .width(200) .height(150) .max_bounces(7) .num_samples(1) .render() .save(format!("video/image_{}.png", frame))?; } else { Renderer::new(&scene, camera) .width(800) .height(600) .max_bounces(9) .num_samples(2000) .render() .save(format!("video/image_{}.png", frame))?; } system.rk4_integrate(&mut cur_state, 1. / 16., 1. / 10000.); println!("Frame {} finished", frame); } Command::new("ffmpeg") .args(&["-y", "-i", "video/image_%d.png", "-vcodec", "libx264"]) .args(&["-s", "800x600", "-pix_fmt", "yuv420p", "video.mp4"]) .spawn()? .wait()?; Ok(()) }