rtiow: add bones of a scene file format based on toml.

This commit is contained in:
Bill Thiede 2023-02-15 14:40:25 -08:00
parent 9353ff675e
commit 37137ac9ca
8 changed files with 152 additions and 8 deletions

View File

@ -18,6 +18,7 @@ fn random_in_unit_disk() -> Vec3 {
}
}
#[derive(Debug)]
pub struct Camera {
origin: Vec3,
lower_left_corner: Vec3,

View File

@ -16,6 +16,7 @@ pub mod material;
pub mod moving_sphere;
pub mod noise;
pub mod output;
pub mod parser;
pub mod ray;
pub mod rect;
pub mod renderer;

View File

@ -0,0 +1,94 @@
use crate::{
camera::Camera,
hitable::Hit,
hitable_list::HitableList,
material::Lambertian,
renderer::Scene,
sphere::Sphere,
texture::{EnvMap, Texture},
};
use serde::Deserialize;
use vec3::Vec3;
#[derive(Debug, Deserialize)]
pub struct Config {
scene: SceneConfig,
camera: CameraConfig,
spheres: Vec<SphereConfig>,
}
impl From<Config> for Scene {
fn from(c: Config) -> Scene {
let material = Lambertian::new([1., 0., 0.]);
let objects: Vec<Box<dyn Hit>> = c
.spheres
.iter()
.map(|sc| -> Box<dyn Hit> {
Box::new(Sphere::new(sc.center, sc.radius, material.clone()))
})
.collect();
let world: Box<dyn Hit> = Box::new(HitableList::new(objects));
let env_map: Option<EnvMap> = None;
let lookfrom = Vec3::new(0., 80., 80.);
let lookat = Vec3::new(0., 0., 0.);
let dist_to_focus = 10.0;
let aperture = 0.0;
let time_min = 0.;
let time_max = 1.;
let camera = Camera::new(
lookfrom,
lookat,
Vec3::new(0., 1., 0.),
45.,
c.scene.width as f32 / c.scene.height as f32,
aperture,
dist_to_focus,
time_min,
time_max,
);
let scene = Scene {
world,
camera,
env_map,
subsamples: c.scene.subsamples.unwrap_or(8),
adaptive_subsampling: c.scene.adaptive_subsampling,
num_threads: c.scene.num_threads,
width: c.scene.width,
height: c.scene.height,
global_illumination: c.scene.global_illumination.unwrap_or(true),
};
dbg!(&scene);
scene
}
}
#[derive(Debug, Deserialize)]
struct SceneConfig {
subsamples: Option<usize>,
adaptive_subsampling: Option<f32>,
num_threads: Option<usize>,
width: usize,
height: usize,
global_illumination: Option<bool>,
}
#[derive(Debug, Deserialize)]
struct SphereConfig {
center: [f32; 3],
radius: f32,
}
#[derive(Debug, Deserialize)]
pub struct CameraConfig {
lookfrom: [f32; 3],
lookat: [f32; 3],
fov: f32,
aspect: f32,
aperture: f32,
focus_dist: f32,
time_min: f32,
time_max: f32,
}

View File

@ -99,6 +99,9 @@ pub struct Opt {
/// Select scene to render.
#[structopt(long = "model")]
pub model: Option<Model>,
/// Toml config describing scene.
#[structopt(long = "config")]
pub config: Option<PathBuf>,
/// Path to store pprof profile data, i.e. /tmp/cpuprofile.pprof
#[structopt(long = "pprof", parse(from_os_str))]
pub pprof: Option<PathBuf>,
@ -126,7 +129,7 @@ pub fn opt_hash(opt: &Opt) -> String {
}
// TODO(wathiede): implement the skips and then the renderer could use json as an input file type.
#[derive(Serialize)]
#[derive(Debug, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct Scene {
#[serde(skip)]

View File

@ -53,7 +53,6 @@ pub fn new(opt: &Opt) -> Scene {
let stl_cube = STL::parse(
BufReader::new(Cursor::new(include_bytes!("../../stls/dragon.stl"))),
//BufReader::new(Cursor::new(include_bytes!("../../stls/cube.stl"))),
false,
)
.expect("failed to parse cube");

View File

@ -7,8 +7,10 @@ edition = "2021"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies]
anyhow = "1.0.69"
log = "0.4.17"
renderer = { path = "../renderer" }
stderrlog = "0.4.3"
structopt = "0.2.18"
strum = "0.24.1"
toml = "0.7.2"

View File

@ -0,0 +1,24 @@
[scene]
width = 768
height = 768
subsamples = 1000
[camera]
lookfrom = [0.0, 10.0, 0.0]
lookat = [0.0, 0.0, 0.0]
fov = 45.0
aspect = 1
aperture = 0.0
focus_dist = 10.0
time_min = 0.0
time_max = 1.0
[[spheres]]
center = [0.0, 0.0, 0.0]
radius = 10
[[spheres]]
center = [30.0, 0.0, 0.0]
radius = 10
[[spheres]]
center = [-30.0, 0.0, 0.0]
radius = 10

View File

@ -1,12 +1,16 @@
#![warn(unused_extern_crates)]
use std::{fs, time::Instant};
use anyhow::Result;
#[cfg(feature = "profile")]
use cpuprofiler::PROFILER;
use log::info;
use structopt::StructOpt;
use renderer::renderer::{render, Model, Opt};
use renderer::{
parser::Config,
renderer::{render, Model, Opt},
};
use strum::VariantNames;
#[cfg(not(feature = "profile"))]
@ -35,7 +39,7 @@ impl MockProfiler {
#[cfg(not(feature = "profile"))]
static PROFILER: MockProfiler = MockProfiler {};
fn main() -> Result<(), std::io::Error> {
fn main() -> Result<()> {
let start_time = Instant::now();
stderrlog::new()
.verbosity(3)
@ -43,12 +47,28 @@ fn main() -> Result<(), std::io::Error> {
.init()
.unwrap();
let opt = Opt::from_args();
if opt.model.is_none() {
eprintln!("--model should be one of {:?}", Model::VARIANTS);
if opt.model.is_none() && opt.config.is_none() {
eprintln!(
"--config <path> or --model should be one of {:?}",
Model::VARIANTS
);
return Ok(());
}
if opt.model.is_some() && opt.config.is_some() {
eprintln!("only specify one of --config or --model");
return Ok(());
}
info!("{:#?}", opt);
let scene = opt.model.as_ref().unwrap().scene(&opt);
let scene = match (&opt.model, &opt.config) {
(Some(model), None) => model.scene(&opt),
(None, Some(config)) => {
let s = std::fs::read_to_string(config)?;
let cfg: Config = toml::from_str(&s)?;
println!("{:#?}", cfg);
cfg.into()
}
_ => unreachable!(),
};
fs::create_dir_all(&opt.output)?;
if opt.pprof.is_some() && !cfg!(feature = "profile") {
panic!("profiling disabled at compile time, but -pprof specified");
@ -68,5 +88,5 @@ fn main() -> Result<(), std::io::Error> {
let time_diff = Instant::now() - start_time;
info!("Total runtime {} seconds", time_diff.as_secs_f32());
res
Ok(res?)
}