summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSakarias Johansson <sakarias.johansson@goodbyekansas.com>2023-03-13 22:00:44 +0100
committerSakarias Johansson <sakarias.johansson@goodbyekansas.com>2023-03-13 22:23:18 +0100
commitf19c8cc40c5caf8abb4f04aaf9f91ec3a8c1ccbc (patch)
treea54a074ece82eafd8793cd0fb68a1b938286c923
parent3cabf77da8b9681ed9683fe92c23054d6f49d848 (diff)
downloadracer-tracer-f19c8cc40c5caf8abb4f04aaf9f91ec3a8c1ccbc.tar.gz
racer-tracer-f19c8cc40c5caf8abb4f04aaf9f91ec3a8c1ccbc.tar.xz
racer-tracer-f19c8cc40c5caf8abb4f04aaf9f91ec3a8c1ccbc.zip
📸 Add Camera defocus blur + Other
Just wanted to add defocus blur but ended up changing a bunch of other this as well. - Moved scenes to a separate folder. - Updated readme with more pretty images. - Add interface for loading scenes. There is currently one for yaml and another if you want a slightly random scene. - Add image action to decide what to do with the final image once its rendered. Currently supports just showing the buffer until you press the render buffer again and saving the image as `png`. - When you use nix shell you will be dropped in the proper folder so you can just do cargo build etc without having to do `cd`.
-rw-r--r--README.md32
-rw-r--r--assets/in_progress.pngbin0 -> 712885 bytes
-rw-r--r--assets/in_progress_2.pngbin0 -> 867755 bytes
-rw-r--r--assets/preview.pngbin0 -> 510732 bytes
-rw-r--r--assets/trace_sample.pngbin627562 -> 1313045 bytes
-rw-r--r--default.nix1
-rw-r--r--racer-tracer/config.yml11
-rw-r--r--racer-tracer/src/camera.rs40
-rw-r--r--racer-tracer/src/config.rs72
-rw-r--r--racer-tracer/src/error.rs8
-rw-r--r--racer-tracer/src/image_action.rs111
-rw-r--r--racer-tracer/src/main.rs76
-rw-r--r--racer-tracer/src/scene.rs111
-rw-r--r--racer-tracer/src/scene/none.rs15
-rw-r--r--racer-tracer/src/scene/random.rs87
-rw-r--r--racer-tracer/src/scene/yml.rs116
-rw-r--r--racer-tracer/src/util.rs20
-rw-r--r--racer-tracer/src/vec3.rs8
-rw-r--r--resources/scenes/three_balls.yml2
19 files changed, 522 insertions, 188 deletions
diff --git a/README.md b/README.md
index b716c16..efbbeef 100644
--- a/README.md
+++ b/README.md
@@ -25,14 +25,44 @@ crate to work.
# Running
The application accepts the following arguments.
`--config` path to the config file.
-`--scene` path to the scene file.
+`--scene` path to the scene file (only supports yml).
+`--image-action` (png, show).
+ Png saves the resulting image to `image_output_dir`.
+ Show just stops the rendering once its done and waits
+ for you to press `R` again to continue with the real-time render.
+
Just running it without any arguments will use the default config and
scene provided by this repository. Once the application starts it will
present a crude preview image of the scene.
+![preview](./assets/preview.png)
+
+## Configuration
+The configuration file has two blocks that controls the preview
+quality and the render quality. You can set the number of samples,
+max_depth etc through threre.
## Controls
`WASD` Moves the camera in a currently crude way.
`R` Starts rendering the image.
+## Rendering Progress
+As you start rendering the image it will replace preview image with a
+more refined one with the settings from the render block in your
+configuration file.
+
+![preview](./assets/preview.png)
+
+
+![in_progress](./assets/in_progress.png)
+
+
+![in_progress2](./assets/in_progress_2.png)
+
+
+![sample](./assets/trace_sample.png)
+
+
+Once the image is done rendering it will go forward with the selected
+image action.
diff --git a/assets/in_progress.png b/assets/in_progress.png
new file mode 100644
index 0000000..bcce09d
--- /dev/null
+++ b/assets/in_progress.png
Binary files differ
diff --git a/assets/in_progress_2.png b/assets/in_progress_2.png
new file mode 100644
index 0000000..adac4dc
--- /dev/null
+++ b/assets/in_progress_2.png
Binary files differ
diff --git a/assets/preview.png b/assets/preview.png
new file mode 100644
index 0000000..3aacd35
--- /dev/null
+++ b/assets/preview.png
Binary files differ
diff --git a/assets/trace_sample.png b/assets/trace_sample.png
index bb07aae..9421fd9 100644
--- a/assets/trace_sample.png
+++ b/assets/trace_sample.png
Binary files differ
diff --git a/default.nix b/default.nix
index 326d701..a14ff49 100644
--- a/default.nix
+++ b/default.nix
@@ -61,5 +61,6 @@ nixpkgs.stdenv.mkDerivation {
shellHook = ''
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:${nixpkgs.lib.makeLibraryPath [ nixpkgs.xorg.libX11 nixpkgs.xorg.libXcursor ] }
export RUST_SRC_PATH=${rustSrcNoSymlinks}
+ cd ${builtins.toString ./.}/racer-tracer
'';
}
diff --git a/racer-tracer/config.yml b/racer-tracer/config.yml
index 0da36dc..e658c1c 100644
--- a/racer-tracer/config.yml
+++ b/racer-tracer/config.yml
@@ -13,9 +13,14 @@ render:
num_threads_height: 10
screen:
- width: 1280
- height: 720
+ width: 640
+ height: 480
-scene: ../resources/scenes/three_balls.yml
+loader:
+ Yml:
+ path: ../resources/scenes/three_balls.yml
image_output_dir: "../"
+
+image_action:
+ SavePng # (SavePng, WaitForSignal)
diff --git a/racer-tracer/src/camera.rs b/racer-tracer/src/camera.rs
index 31c2391..e6396e8 100644
--- a/racer-tracer/src/camera.rs
+++ b/racer-tracer/src/camera.rs
@@ -1,13 +1,12 @@
use crate::image::Image;
use crate::ray::Ray;
-use crate::util::degrees_to_radians;
+use crate::util::{degrees_to_radians, random_in_unit_disk};
use crate::vec3::Vec3;
#[derive(Clone)]
pub struct Camera {
pub viewport_height: f64,
pub viewport_width: f64,
- pub focal_length: f64,
pub origin: Vec3,
pub horizontal: Vec3,
pub vertical: Vec3,
@@ -15,6 +14,8 @@ pub struct Camera {
pub forward: Vec3,
pub right: Vec3,
pub up: Vec3,
+ pub lens_radius: f64,
+ pub focus_distance: f64,
}
impl Camera {
@@ -24,7 +25,8 @@ impl Camera {
up: Vec3,
vfov: f64,
image: &Image,
- focal_length: f64,
+ aperture: f64,
+ focus_distance: f64,
) -> Camera {
let h = (degrees_to_radians(vfov) / 2.0).tan();
let viewport_height = 2.0 * h;
@@ -34,45 +36,47 @@ impl Camera {
let right = up.cross(&forward).unit_vector();
let up = forward.cross(&right);
- let horizontal = viewport_width * right;
- let vertical = viewport_height * up;
-
+ let horizontal = focus_distance * viewport_width * right;
+ let vertical = focus_distance * viewport_height * up;
Camera {
viewport_height,
viewport_width,
- focal_length,
origin: look_from,
horizontal,
vertical,
- upper_left_corner: look_from + vertical / 2.0 - horizontal / 2.0 - forward,
+ upper_left_corner: look_from + vertical / 2.0
+ - horizontal / 2.0
+ - focus_distance * forward,
forward,
right,
up,
+ lens_radius: aperture * 0.5,
+ focus_distance,
}
}
pub fn get_ray(&self, u: f64, v: f64) -> Ray {
+ let ray_direction = self.lens_radius * random_in_unit_disk();
+ let offset = self.right * ray_direction.x() + self.up * ray_direction.y();
Ray::new(
- self.origin,
- self.upper_left_corner + u * self.horizontal - v * self.vertical - self.origin,
+ self.origin + offset,
+ self.upper_left_corner + u * self.horizontal - v * self.vertical - self.origin - offset,
)
}
- // TODO: Add support for rotation
-
- // TODO: Use forward facing vector
pub fn go_forward(&mut self, go: f64) {
self.origin += self.forward * go;
- self.upper_left_corner =
- self.origin + self.vertical / 2.0 - self.horizontal / 2.0 - self.forward;
+ self.upper_left_corner = self.origin + self.vertical / 2.0
+ - self.horizontal / 2.0
+ - self.focus_distance * self.forward;
}
- // TODO: Use right facing vector
pub fn go_right(&mut self, go: f64) {
self.origin += self.right * go;
- self.upper_left_corner =
- self.origin + self.vertical / 2.0 - self.horizontal / 2.0 - self.forward;
+ self.upper_left_corner = self.origin + self.vertical / 2.0
+ - self.horizontal / 2.0
+ - self.focus_distance * self.forward;
}
}
diff --git a/racer-tracer/src/config.rs b/racer-tracer/src/config.rs
index 7bd7887..4cb0880 100644
--- a/racer-tracer/src/config.rs
+++ b/racer-tracer/src/config.rs
@@ -1,4 +1,4 @@
-use std::path::PathBuf;
+use std::{path::PathBuf, str::FromStr};
use config::File;
use serde::Deserialize;
@@ -34,20 +34,77 @@ pub struct Args {
#[structopt(short = "s", long = "scene")]
pub scene: Option<String>,
+
+ #[structopt(long = "image-action")]
+ pub image_action: Option<ImageAction>,
}
impl TryFrom<Args> for Config {
type Error = TracerError;
fn try_from(args: Args) -> Result<Self, TracerError> {
- Config::from_file(args.config).map(|mut cfg| {
- if args.scene.is_some() {
- cfg.scene = args.scene;
+ Config::from_file(args.config).and_then(|mut cfg| {
+ if let Some(image_action) = args.image_action {
+ cfg.image_action = image_action;
+ }
+
+ if let Some(scene) = args.scene {
+ if scene == "random" {
+ cfg.loader = SceneLoader::Random;
+ } else {
+ let path = PathBuf::from(scene);
+ cfg.loader = path
+ .extension()
+ .map(|s| s.to_string_lossy())
+ .ok_or_else(|| {
+ TracerError::ArgumentParsingError(format!(
+ "Could not get extension from scene file: {}",
+ path.display()
+ ))
+ })
+ .and_then(|p| match p.as_ref() {
+ "yml" => Ok(SceneLoader::Yml { path: path.clone() }),
+ _ => Err(TracerError::ArgumentParsingError(format!(
+ "Could not find a suitable scene loader for file: {}",
+ path.display()
+ ))),
+ })?;
+ };
}
- cfg
+
+ Ok(cfg)
})
}
}
+#[derive(StructOpt, Debug, Clone, Deserialize, Default)]
+pub enum SceneLoader {
+ #[default]
+ None,
+ Yml {
+ path: PathBuf,
+ },
+ Random,
+}
+
+#[derive(StructOpt, Debug, Clone, Deserialize, Default)]
+pub enum ImageAction {
+ #[default]
+ WaitForSignal,
+ SavePng,
+}
+
+impl FromStr for ImageAction {
+ type Err = TracerError;
+
+ fn from_str(s: &str) -> Result<Self, Self::Err> {
+ match s {
+ "png" => Ok(ImageAction::SavePng),
+ "show" => Ok(ImageAction::WaitForSignal),
+ _ => Ok(ImageAction::WaitForSignal),
+ }
+ }
+}
+
#[derive(Default, Debug, Deserialize)]
pub struct Config {
#[serde(default)]
@@ -60,7 +117,10 @@ pub struct Config {
pub screen: Screen,
#[serde(default)]
- pub scene: Option<String>,
+ pub loader: SceneLoader,
+
+ #[serde(default)]
+ pub image_action: ImageAction,
#[serde(default)]
pub image_output_dir: Option<PathBuf>,
diff --git a/racer-tracer/src/error.rs b/racer-tracer/src/error.rs
index 1725d1a..e0e0934 100644
--- a/racer-tracer/src/error.rs
+++ b/racer-tracer/src/error.rs
@@ -20,6 +20,9 @@ pub enum TracerError {
#[error("Config Error ({0}): {1}")]
Configuration(String, String),
+ #[error("Argument parsing Error: {0}")]
+ ArgumentParsingError(String),
+
#[error("Unknown Material {0}.")]
UnknownMaterial(String),
@@ -37,6 +40,9 @@ pub enum TracerError {
#[error("Image save error: {0}")]
ImageSave(String),
+
+ #[error("Scene failed to load: {0}")]
+ SceneLoad(String),
}
impl From<TracerError> for i32 {
@@ -57,6 +63,8 @@ impl From<TracerError> for i32 {
TracerError::CancelEvent => 10,
TracerError::Generic(_) => 11,
TracerError::ImageSave(_) => 12,
+ TracerError::SceneLoad(_) => 13,
+ TracerError::ArgumentParsingError(_) => 14,
}
}
}
diff --git a/racer-tracer/src/image_action.rs b/racer-tracer/src/image_action.rs
new file mode 100644
index 0000000..825c506
--- /dev/null
+++ b/racer-tracer/src/image_action.rs
@@ -0,0 +1,111 @@
+use std::{path::PathBuf, sync::RwLock};
+
+use sha2::{Digest, Sha256};
+use synchronoise::SignalEvent;
+
+use crate::{
+ config::{Config, ImageAction as CImageAction},
+ error::TracerError,
+};
+
+pub trait ImageAction: Send + Sync {
+ fn action(
+ &self,
+ screen_buffer: &RwLock<Vec<u32>>,
+ event: &SignalEvent,
+ config: &Config,
+ ) -> Result<(), TracerError>;
+}
+
+impl From<&CImageAction> for Box<dyn ImageAction> {
+ fn from(image_action: &CImageAction) -> Self {
+ match image_action {
+ CImageAction::WaitForSignal => Box::new(WaitForSignal::new()),
+ CImageAction::SavePng => Box::new(SavePng::new()),
+ }
+ }
+}
+
+pub struct SavePng {}
+
+impl SavePng {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl ImageAction for SavePng {
+ fn action(
+ &self,
+ screen_buffer: &RwLock<Vec<u32>>,
+ _event: &SignalEvent,
+ config: &Config,
+ ) -> Result<(), TracerError> {
+ screen_buffer
+ .read()
+ .map_err(|e| TracerError::FailedToAcquireLock(e.to_string()))
+ .map(|buf| {
+ // Convert ARGB8 to RGBA8
+ buf.iter()
+ .map(|v| {
+ let a: u32 = (v >> 24) & 0xff;
+ let r: u32 = (v >> 16) & 0xff;
+ let g: u32 = (v >> 8) & 0xff;
+ let b: u32 = v & 0xff;
+
+ (r << 24) | (g << 16) | (b << 8) | a
+ })
+ .flat_map(|val| val.to_be_bytes())
+ .collect::<Vec<u8>>()
+ })
+ .and_then(|buf| match &config.image_output_dir {
+ Some(image_dir) => {
+ println!("Saving image...");
+ let mut sha = Sha256::new();
+
+ sha.update(buf.as_slice());
+
+ let mut file_path = PathBuf::from(image_dir);
+ file_path.push(format!("{:X}.png", sha.finalize()));
+
+ img::save_buffer(
+ file_path.as_path(),
+ buf.as_slice(),
+ config.screen.width as u32,
+ config.screen.height as u32,
+ img::ColorType::Rgba8,
+ )
+ .map_err(|e| {
+ let error = e.to_string();
+ TracerError::ImageSave(error)
+ })
+ .map(|_| {
+ println!("Saved image to: {}", file_path.to_string_lossy());
+ })
+ }
+ None => Ok(()),
+ })
+ }
+}
+
+pub struct WaitForSignal {}
+
+impl WaitForSignal {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl ImageAction for WaitForSignal {
+ fn action(
+ &self,
+ _screen_buffer: &RwLock<Vec<u32>>,
+ event: &SignalEvent,
+ _config: &Config,
+ ) -> Result<(), TracerError> {
+ println!("Press R to resume.");
+ event.wait();
+ event.reset();
+ Ok(())
+ }
+}
diff --git a/racer-tracer/src/main.rs b/racer-tracer/src/main.rs
index 5612649..6648c81 100644
--- a/racer-tracer/src/main.rs
+++ b/racer-tracer/src/main.rs
@@ -4,6 +4,7 @@ mod camera;
mod config;
mod geometry;
mod image;
+mod image_action;
mod material;
mod ray;
mod render;
@@ -15,14 +16,13 @@ extern crate image as img;
use std::{
convert::TryFrom,
- path::PathBuf,
sync::RwLock,
time::{Duration, Instant},
vec::Vec,
};
+use image_action::ImageAction;
use minifb::{Key, Window, WindowOptions};
-use sha2::{Digest, Sha256};
use synchronoise::SignalEvent;
use crate::vec3::Vec3;
@@ -38,18 +38,19 @@ use crate::{
fn run(config: Config) -> Result<(), TracerError> {
let image = image::Image::new(config.screen.width, config.screen.height);
let screen_buffer: RwLock<Vec<u32>> = RwLock::new(vec![0; image.width * image.height]);
+ let look_from = Vec3::new(13.0, 2.0, 3.0);
+ let look_at = Vec3::new(0.0, 0.0, 0.0);
let camera = RwLock::new(Camera::new(
- Vec3::new(-2.0, 2.0, 1.0),
- Vec3::new(0.0, 0.0, -1.0),
+ look_from,
+ look_at,
Vec3::new(0.0, 1.0, 0.0),
- 90.0,
+ 20.0,
&image,
- 1.0,
+ 0.1,
+ 10.0,
));
- let scene: Scene = config
- .scene
- .ok_or(TracerError::NoScene())
- .and_then(Scene::from_file)?;
+
+ let scene = Scene::try_new((&config.loader).into())?;
let mut window_res: Result<(), TracerError> = Ok(());
let mut render_res: Result<(), TracerError> = Ok(());
@@ -58,6 +59,8 @@ fn run(config: Config) -> Result<(), TracerError> {
let cancel_render = SignalEvent::manual(false);
let exit = SignalEvent::manual(false);
+ let image_action: Box<dyn ImageAction> = (&config.image_action).into();
+
rayon::scope(|s| {
s.spawn(|_| {
while render_res.is_ok() {
@@ -94,62 +97,11 @@ fn run(config: Config) -> Result<(), TracerError> {
)
.and_then(|_| {
render_image.reset();
-
println!(
"It took {} seconds to render the image.",
Instant::now().duration_since(render_time).as_secs()
);
- screen_buffer
- .read()
- .map_err(|e| {
- TracerError::FailedToAcquireLock(e.to_string())
- })
- .map(|buf| {
- // Convert ARGB8 to RGBA8
- buf.iter()
- .map(|v| {
- let a: u32 = (v >> 24) & 0xff;
- let r: u32 = (v >> 16) & 0xff;
- let g: u32 = (v >> 8) & 0xff;
- let b: u32 = v & 0xff;
-
- (r << 24) | (g << 16) | (b << 8) | a
- })
- .flat_map(|val| val.to_be_bytes())
- .collect::<Vec<u8>>()
- })
- })
- .and_then(|buf| {
- match &config.image_output_dir {
- Some(image_dir) => {
- println!("Saving image...");
- let mut sha = Sha256::new();
-
- sha.update(buf.as_slice());
-
- let mut file_path = PathBuf::from(image_dir);
- file_path.push(format!("{:X}.png", sha.finalize()));
-
- img::save_buffer(
- file_path.as_path(),
- buf.as_slice(),
- config.screen.width as u32,
- config.screen.height as u32,
- img::ColorType::Rgba8,
- )
- .map_err(|e| {
- let error = e.to_string();
- TracerError::ImageSave(error)
- })
- .map(|_| {
- println!(
- "Saved image to: {}",
- file_path.to_string_lossy()
- );
- })
- }
- None => Ok(()),
- }
+ image_action.action(&screen_buffer, &render_image, &config)
})
},
)
diff --git a/racer-tracer/src/scene.rs b/racer-tracer/src/scene.rs
index c0c6bb7..12e2296 100644
--- a/racer-tracer/src/scene.rs
+++ b/racer-tracer/src/scene.rs
@@ -1,13 +1,10 @@
-use std::{collections::HashMap, path::Path, sync::Arc};
-
-use config::File;
-use serde::Deserialize;
+pub mod none;
+pub mod random;
+pub mod yml;
use crate::{
- error::TracerError,
- geometry::{sphere::Sphere, Hittable},
- material::{dialectric::Dialectric, lambertian::Lambertian, metal::Metal, SharedMaterial},
- vec3::{Color, Vec3},
+ config::SceneLoader as CSLoader, error::TracerError, geometry::Hittable,
+ scene::none::NoneLoader, scene::random::Random, scene::yml::YmlLoader,
};
pub struct Scene {
@@ -16,20 +13,14 @@ pub struct Scene {
impl Scene {
#[allow(dead_code)]
- pub fn new() -> Self {
- Self {
- objects: Vec::new(),
- }
+ pub fn try_new(loader: Box<dyn SceneLoader>) -> Result<Self, TracerError> {
+ loader.load().map(|objects| Self { objects })
}
#[allow(dead_code)]
pub fn add(&mut self, hittable: Box<dyn Hittable>) {
self.objects.push(hittable);
}
-
- pub fn from_file<P: AsRef<Path>>(file: P) -> Result<Self, TracerError> {
- SceneData::from_file(file)?.try_into()
- }
}
impl Hittable for Scene {
@@ -53,86 +44,16 @@ impl Hittable for Scene {
}
}
-#[derive(Debug, Deserialize)]
-enum MaterialData {
- Lambertian { color: Color },
- Metal { color: Color, fuzz: f64 },
- Dialectric { refraction_index: f64 },
-}
-
-#[derive(Debug, Deserialize)]
-enum GeometryData {
- Sphere {
- pos: Vec3,
- radius: f64,
- material: String,
- },
-}
-
-#[derive(Deserialize)]
-struct SceneData {
- materials: HashMap<String, MaterialData>,
- geometry: Vec<GeometryData>,
-}
-
-impl SceneData {
- pub fn from_file<P: AsRef<Path>>(file: P) -> Result<Self, TracerError> {
- config::Config::builder()
- .add_source(File::from(file.as_ref()))
- .build()
- .map_err(|e| {
- TracerError::Configuration(
- file.as_ref().to_string_lossy().into_owned(),
- e.to_string(),
- )
- })?
- .try_deserialize()
- .map_err(|e| {
- TracerError::Configuration(
- file.as_ref().to_string_lossy().into_owned(),
- e.to_string(),
- )
- })
- }
+pub trait SceneLoader: Send + Sync {
+ fn load(&self) -> Result<Vec<Box<dyn Hittable>>, TracerError>;
}
-impl TryInto<Scene> for SceneData {
- type Error = TracerError;
- fn try_into(self) -> Result<Scene, TracerError> {
- let mut materials: HashMap<String, SharedMaterial> = HashMap::new();
- self.materials
- .into_iter()
- .for_each(|(id, material)| match material {
- MaterialData::Lambertian { color } => {
- materials.insert(id, Arc::new(Box::new(Lambertian::new(color))));
- }
- MaterialData::Metal { color, fuzz } => {
- materials.insert(id, Arc::new(Box::new(Metal::new(color, fuzz))));
- }
- MaterialData::Dialectric { refraction_index } => {
- materials.insert(id, Arc::new(Box::new(Dialectric::new(refraction_index))));
- }
- });
-
- let geometry: Vec<Box<dyn Hittable>> = self
- .geometry
- .into_iter()
- .map(|geo| match geo {
- GeometryData::Sphere {
- pos,
- radius,
- material,
- } => materials
- .get(&material)
- .ok_or(TracerError::UnknownMaterial(material))
- .map(|mat| {
- let apa: Box<dyn Hittable> =
- Box::new(Sphere::new(pos, radius, Arc::clone(mat)));
- apa
- }),
- })
- .collect::<Result<Vec<Box<dyn Hittable>>, TracerError>>()?;
-
- Ok(Scene { objects: geometry })
+impl From<&CSLoader> for Box<dyn SceneLoader> {
+ fn from(loader: &CSLoader) -> Self {
+ match loader {
+ CSLoader::Yml { path } => Box::new(YmlLoader::new(path.clone())),
+ CSLoader::Random => Box::new(Random::new()),
+ CSLoader::None => Box::new(NoneLoader::new()),
+ }
}
}
diff --git a/racer-tracer/src/scene/none.rs b/racer-tracer/src/scene/none.rs
new file mode 100644
index 0000000..2aed105
--- /dev/null
+++ b/racer-tracer/src/scene/none.rs
@@ -0,0 +1,15 @@
+use crate::{error::TracerError, scene::SceneLoader};
+
+pub struct NoneLoader {}
+
+impl NoneLoader {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl SceneLoader for NoneLoader {
+ fn load(&self) -> Result<Vec<Box<dyn crate::geometry::Hittable>>, TracerError> {
+ Ok(Vec::new())
+ }
+}
diff --git a/racer-tracer/src/scene/random.rs b/racer-tracer/src/scene/random.rs
new file mode 100644
index 0000000..ddbcfc1
--- /dev/null
+++ b/racer-tracer/src/scene/random.rs
@@ -0,0 +1,87 @@
+use std::sync::Arc;
+
+use crate::{
+ error::TracerError,
+ geometry::{sphere::Sphere, Hittable},
+ material::{dialectric::Dialectric, lambertian::Lambertian, metal::Metal, SharedMaterial},
+ scene::SceneLoader,
+ util::{random_double, random_double_range},
+ vec3::{Color, Vec3},
+};
+
+pub struct Random {}
+
+impl Random {
+ pub fn new() -> Self {
+ Self {}
+ }
+}
+
+impl SceneLoader for Random {
+ fn load(&self) -> Result<Vec<Box<dyn crate::geometry::Hittable>>, TracerError> {
+ let mut geometry: Vec<Box<dyn Hittable>> = Vec::new();
+ let ground_material: SharedMaterial =
+ Arc::new(Box::new(Lambertian::new(Color::new(0.5, 0.5, 0.5))));
+ geometry.push(Box::new(Sphere::new(
+ Vec3::new(0.0, -1000.0, 0.0),
+ 1000.0,
+ ground_material,
+ )));
+
+ for a in -11..11 {
+ for b in -11..11 {
+ let choose_mat = random_double();
+ let center = Vec3::new(
+ a as f64 + 0.9 * random_double(),
+ 0.2,
+ b as f64 + 0.9 * random_double(),
+ );
+
+ if (center - Vec3::new(4.0, 0.2, 0.0)).length() > 0.9 {
+ if choose_mat < 0.8 {
+ // diffuse
+ let albedo = Color::random() * Color::random();
+ let mat: SharedMaterial = Arc::new(Box::new(Lambertian::new(albedo)));
+ geometry.push(Box::new(Sphere::new(center, 0.2, mat)));
+ } else if choose_mat > 0.95 {
+ // metal
+ let albedo = Color::random_range(0.5, 1.0);
+ let fuzz = random_double_range(0.0, 0.5);
+ let mat: SharedMaterial = Arc::new(Box::new(Metal::new(albedo, fuzz)));
+ geometry.push(Box::new(Sphere::new(center, 0.2, mat)));
+ } else {
+ // glass
+ let mat: SharedMaterial = Arc::new(Box::new(Dialectric::new(1.5)));
+ geometry.push(Box::new(Sphere::new(center, 0.2, mat)));
+ }
+ }
+ }
+ }
+
+ let dial_mat: SharedMaterial = Arc::new(Box::new(Dialectric::new(1.5)));
+ geometry.push(Box::new(Sphere::new(
+ Vec3::new(0.0, 1.0, 0.0),
+ 1.0,
+ dial_mat,
+ )));
+
+ let lamb_mat: SharedMaterial =
+ Arc::new(Box::new(Lambertian::new(Color::new(0.4, 0.2, 0.1))));
+ geometry.push(Box::new(Sphere::new(
+ Vec3::new(-4.0, 1.0, 0.0),
+ 1.0,
+ lamb_mat,
+ )));
+
+ let metal_mat: SharedMaterial =
+ Arc::new(Box::new(Metal::new(Color::new(0.7, 0.6, 0.5), 0.0)));
+
+ geometry.push(Box::new(Sphere::new(
+ Vec3::new(4.0, 1.0, 0.0),
+ 1.0,
+ metal_mat,
+ )));
+
+ Ok(geometry)
+ }
+}
diff --git a/racer-tracer/src/scene/yml.rs b/racer-tracer/src/scene/yml.rs
new file mode 100644
index 0000000..3e69786
--- /dev/null
+++ b/racer-tracer/src/scene/yml.rs
@@ -0,0 +1,116 @@
+use std::{
+ collections::HashMap,
+ path::{Path, PathBuf},
+ sync::Arc,
+};
+
+use serde::Deserialize;
+
+use crate::{
+ error::TracerError,
+ geometry::{sphere::Sphere, Hittable},
+ material::{dialectric::Dialectric, lambertian::Lambertian, metal::Metal, SharedMaterial},
+ scene::SceneLoader,
+ vec3::{Color, Vec3},
+};
+
+use config::File;
+
+pub struct YmlLoader {
+ path: PathBuf,
+}
+
+impl YmlLoader {
+ pub fn new(path: PathBuf) -> Self {
+ Self { path }
+ }
+}
+
+impl SceneLoader for YmlLoader {
+ fn load(&self) -> Result<Vec<Box<dyn crate::geometry::Hittable>>, TracerError> {
+ let datta = SceneData::from_file(PathBuf::from(&self.path))?;
+ datta.try_into()
+ }
+}
+
+#[derive(Debug, Deserialize)]
+enum MaterialData {
+ Lambertian { color: Color },
+ Metal { color: Color, fuzz: f64 },
+ Dialectric { refraction_index: f64 },
+}
+
+#[derive(Debug, Deserialize)]
+enum GeometryData {
+ Sphere {
+ pos: Vec3,
+ radius: f64,
+ material: String,
+ },
+}
+
+#[derive(Deserialize)]
+struct SceneData {
+ materials: HashMap<String, MaterialData>,
+ geometry: Vec<GeometryData>,
+}
+
+impl SceneData {
+ pub fn from_file<P: AsRef<Path>>(file: P) -> Result<Self, TracerError> {
+ config::Config::builder()
+ .add_source(File::from(file.as_ref()))
+ .build()
+ .map_err(|e| {
+ TracerError::Configuration(
+ file.as_ref().to_string_lossy().into_owned(),
+ dbg!(e).to_string(),
+ )
+ })?
+ .try_deserialize()
+ .map_err(|e| {
+ TracerError::Configuration(
+ file.as_ref().to_string_lossy().into_owned(),
+ dbg!(e).to_string(),
+ )
+ })
+ }
+}
+
+impl TryInto<Vec<Box<dyn Hittable>>> for SceneData {
+ type Error = TracerError;
+ fn try_into(self) -> Result<Vec<Box<dyn Hittable>>, TracerError> {
+ let mut materials: HashMap<String, SharedMaterial> = HashMap::new();
+ self.materials
+ .into_iter()
+ .for_each(|(id, material)| match material {
+ MaterialData::Lambertian { color } => {
+ materials.insert(id, Arc::new(Box::new(Lambertian::new(color))));
+ }
+ MaterialData::Metal { color, fuzz } => {
+ materials.insert(id, Arc::new(Box::new(Metal::new(color, fuzz))));
+ }
+ MaterialData::Dialectric { refraction_index } => {
+ materials.insert(id, Arc::new(Box::new(Dialectric::new(refraction_index))));
+ }
+ });
+
+ let geometry: Vec<Box<dyn Hittable>> = self
+ .geometry
+ .into_iter()
+ .map(|geo| match geo {
+ GeometryData::Sphere {
+ pos,
+ radius,
+ material,
+ } => materials
+ .get(&material)
+ .ok_or(TracerError::UnknownMaterial(material))
+ .map(|mat| {
+ Box::new(Sphere::new(pos, radius, Arc::clone(mat))) as Box<dyn Hittable>
+ }),
+ })
+ .collect::<Result<Vec<Box<dyn Hittable>>, TracerError>>()?;
+
+ Ok(geometry)
+ }
+}
diff --git a/racer-tracer/src/util.rs b/racer-tracer/src/util.rs
index 2f04567..5fb2d36 100644
--- a/racer-tracer/src/util.rs
+++ b/racer-tracer/src/util.rs
@@ -1,6 +1,7 @@
use rand::Rng;
-// For later use
+use crate::vec3::Vec3;
+
pub fn degrees_to_radians(degrees: f64) -> f64 {
degrees * std::f64::consts::PI / 180.0
}
@@ -10,8 +11,23 @@ pub fn random_double() -> f64 {
rng.gen::<f64>()
}
-// For later use
pub fn random_double_range(min: f64, max: f64) -> f64 {
let mut rng = rand::thread_rng();
rng.gen_range(min..max)
}
+
+pub fn random_in_unit_disk() -> Vec3 {
+ // TODO: This feels not nice
+ loop {
+ let p = Vec3::new(
+ random_double_range(-1.0, 1.0),
+ random_double_range(-1.0, 1.0),
+ 0.0,
+ );
+ if p.length_squared() >= 1.0 {
+ continue;
+ }
+
+ return p;
+ }
+}
diff --git a/racer-tracer/src/vec3.rs b/racer-tracer/src/vec3.rs
index 8530bfe..e315203 100644
--- a/racer-tracer/src/vec3.rs
+++ b/racer-tracer/src/vec3.rs
@@ -275,6 +275,14 @@ impl ops::Mul<Vec3> for f64 {
}
}
+impl ops::Mul<&f64> for Vec3 {
+ type Output = Vec3;
+
+ fn mul(self, rhs: &f64) -> Self::Output {
+ Vec3::new(self.data[0] * rhs, self.data[1] * rhs, self.data[2] * rhs)
+ }
+}
+
impl ops::MulAssign<f64> for Vec3 {
fn mul_assign(&mut self, rhs: f64) {
self.data[0] *= rhs;
diff --git a/resources/scenes/three_balls.yml b/resources/scenes/three_balls.yml
index f9e7571..65b1393 100644
--- a/resources/scenes/three_balls.yml
+++ b/resources/scenes/three_balls.yml
@@ -17,7 +17,7 @@ materials:
right:
Metal:
color:
- data: [ 0.8, 0.6, 0.2]
+ data: [ 0.8, 0.6, 0.2 ]
fuzz: 0.0
geometry: