-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
The `geom::cast` module has been migrated across. To support this I have also migrated the CSV reader to read into a Table object. This commit does not include documentation or unit tests. The progress of these can be tracked on issues #24 and #25 respectively.
- Loading branch information
1 parent
9e02998
commit 58441a7
Showing
8 changed files
with
476 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,41 @@ | ||
//! Comma-Separated-Variable file handling. | ||
|
||
use crate::{data::Table, err::Error, fs::File}; | ||
use std::{ | ||
io::{BufRead, BufReader}, | ||
path::Path, | ||
str::FromStr, | ||
}; | ||
|
||
impl<T: FromStr> File for Table<T> { | ||
#[inline] | ||
fn load(path: &Path) -> Result<Self, Error> { | ||
// Load all of the lines into a vector of lines. | ||
let mut lines: Vec<_> = BufReader::new(std::fs::File::open(path)?) | ||
.lines() | ||
.map(Result::unwrap) | ||
.filter(|line| !line.starts_with("//")) | ||
.collect(); | ||
|
||
// As we know the number of rows, we can pre-allocate the rows vector. | ||
let mut rows = Vec::with_capacity(lines.len()); | ||
// We make the reasonable assumption that the CSV file has a header on the first row. | ||
let headings = lines | ||
.remove(0) | ||
.split(',') | ||
.map(|s| (*s).to_string()) | ||
.collect(); | ||
// Now iterate the remaining lines, attempt to parse them and push them onto the rows vec. | ||
for mut line in lines { | ||
line.retain(|c| !c.is_whitespace()); | ||
let row = line | ||
.split(',') | ||
.map(str::parse) | ||
.filter_map(Result::ok) | ||
.collect(); | ||
rows.push(row); | ||
} | ||
|
||
Ok(Self::new(headings, rows)) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
//! Camera structure. | ||
|
||
use crate::{ | ||
access, clone, fmt_report, | ||
geom::{Orient, Ray}, | ||
math::{Point3, Rot3, Vec3}, | ||
ord::{X, Y}, | ||
}; | ||
use std::fmt::{Display, Error, Formatter}; | ||
|
||
/// Tracer emission structure. | ||
pub struct Camera { | ||
/// Orientation. | ||
orient: Orient, | ||
/// Rotation delta. | ||
half_delta_theta: f64, | ||
/// Resolution. | ||
res: [usize; 2], | ||
/// Super sampling power. | ||
ss_power: usize, | ||
} | ||
|
||
impl Camera { | ||
access!(res: [usize; 2]); | ||
clone!(ss_power: usize); | ||
|
||
/// Construct a new instance. | ||
#[inline] | ||
#[must_use] | ||
pub fn new(orient: Orient, fov: f64, res: [usize; 2], ss_power: usize) -> Self { | ||
debug_assert!(fov > 0.0); | ||
debug_assert!(res[X] > 0); | ||
debug_assert!(res[Y] > 0); | ||
debug_assert!(ss_power > 0); | ||
|
||
let half_delta_theta = fov / ((2 * (ss_power * (res[X] - 1))) as f64); | ||
|
||
Self { | ||
orient, | ||
half_delta_theta, | ||
res, | ||
ss_power, | ||
} | ||
} | ||
|
||
/// Reference the camera's position. | ||
#[inline] | ||
#[must_use] | ||
pub const fn pos(&self) -> &Point3 { | ||
self.orient.pos() | ||
} | ||
|
||
/// Calculate the total number of samples. | ||
#[inline] | ||
#[must_use] | ||
pub const fn num_pixels(&self) -> usize { | ||
self.res[X] * self.res[Y] | ||
} | ||
|
||
/// Calculate the total number of super samples per pixel. | ||
#[inline] | ||
#[must_use] | ||
pub const fn num_super_samples(&self) -> usize { | ||
self.ss_power * self.ss_power | ||
} | ||
|
||
/// Calculate the total number of samples. | ||
#[inline] | ||
#[must_use] | ||
pub const fn num_samples(&self) -> usize { | ||
self.num_super_samples() * self.num_pixels() as usize | ||
} | ||
|
||
/// Emit a ray for the given pixel and super-sample. | ||
#[inline] | ||
#[must_use] | ||
pub fn emit(&self, pixel: [usize; 2], ss: [usize; 2]) -> Ray { | ||
debug_assert!(pixel[X] < self.res[X]); | ||
debug_assert!(pixel[Y] < self.res[Y]); | ||
debug_assert!(ss[X] < self.ss_power); | ||
debug_assert!(ss[Y] < self.ss_power); | ||
|
||
let mut theta = | ||
self.half_delta_theta * (1 + (2 * (ss[X] + (pixel[X] * self.ss_power)))) as f64; | ||
let mut phi = | ||
self.half_delta_theta * (1 + (2 * (ss[Y] + (pixel[Y] * self.ss_power)))) as f64; | ||
|
||
theta -= self.half_delta_theta * (self.res[X] * self.ss_power) as f64; | ||
phi -= self.half_delta_theta * (self.res[Y] * self.ss_power) as f64; | ||
|
||
let mut ray = self.orient.forward_ray(); | ||
*ray.dir_mut() = Rot3::from_axis_angle(&Vec3::from(self.orient.down()), theta) | ||
* Rot3::from_axis_angle(&Vec3::from(*self.orient.right()), phi) | ||
* ray.dir(); | ||
|
||
ray | ||
} | ||
} | ||
|
||
impl Display for Camera { | ||
#[inline] | ||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { | ||
writeln!(fmt, "...")?; | ||
fmt_report!(fmt, self.orient, "orientation"); | ||
fmt_report!(fmt, self.half_delta_theta.to_degrees(), "dTheta/2 (deg)"); | ||
fmt_report!( | ||
fmt, | ||
&format!("[{} x {}]", self.res[X], self.res[Y]), | ||
"resolution" | ||
); | ||
fmt_report!(fmt, self.ss_power, "super sampling power"); | ||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
//! Camera builder structure. | ||
|
||
use crate::{ | ||
fmt_report, | ||
geom::{Camera, Orient}, | ||
math::{Point3, Vec3}, | ||
ord::{Build, X, Y}, | ||
}; | ||
use arctk_attr::file; | ||
use std::fmt::{Display, Error, Formatter}; | ||
|
||
/// Loadable camera structure. | ||
#[file] | ||
#[derive(Clone)] | ||
pub struct CameraBuilder { | ||
/// Position. | ||
pos: Point3, | ||
/// Target. | ||
tar: Point3, | ||
/// Horizontal field-of-view (deg). | ||
fov: f64, | ||
/// Image resolution. | ||
res: [usize; 2], | ||
/// Optional super-sampling power. | ||
ss_power: Option<usize>, | ||
} | ||
|
||
impl CameraBuilder { | ||
/// Construct a new instance. | ||
#[inline] | ||
#[must_use] | ||
pub fn new(pos: Point3, tar: Point3, fov: f64, res: [usize; 2], ss_power: Option<usize>) -> Self { | ||
debug_assert!(fov > 0.0); | ||
debug_assert!(res[X] > 0); | ||
debug_assert!(res[Y] > 0); | ||
debug_assert!(ss_power.is_none() || ss_power.unwrap() > 1); | ||
|
||
Self { | ||
pos, | ||
tar, | ||
fov, | ||
res, | ||
ss_power, | ||
} | ||
} | ||
|
||
/// Move the camera. | ||
#[inline] | ||
pub fn travel(&mut self, d: Vec3) { | ||
self.pos += d; | ||
} | ||
} | ||
|
||
impl Build for CameraBuilder { | ||
type Inst = Camera; | ||
|
||
#[inline] | ||
fn build(self) -> Self::Inst { | ||
Self::Inst::new( | ||
Orient::new_tar(self.pos, &self.tar), | ||
self.fov.to_radians(), | ||
self.res, | ||
self.ss_power.map_or(1, |ss| ss), | ||
) | ||
} | ||
} | ||
|
||
impl Display for CameraBuilder { | ||
#[inline] | ||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { | ||
writeln!(fmt, "...")?; | ||
fmt_report!( | ||
fmt, | ||
&format!("({}, {}, {})", self.pos.x(), self.pos.y(), self.pos.z()), | ||
"position (m)" | ||
); | ||
fmt_report!( | ||
fmt, | ||
&format!("({}, {}, {})", self.tar.x(), self.tar.y(), self.tar.z()), | ||
"target (m)" | ||
); | ||
fmt_report!(fmt, self.fov, "field of view (deg)"); | ||
fmt_report!( | ||
fmt, | ||
&format!("[{} x {}]", self.res[X], self.res[Y]), | ||
"resolution" | ||
); | ||
|
||
let ss_power = if let Some(n) = self.ss_power { | ||
format!("{} sub-samples", n * n) | ||
} else { | ||
"OFF".to_owned() | ||
}; | ||
fmt_report!(fmt, ss_power, "super sampling"); | ||
|
||
Ok(()) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
//! Optical material. | ||
|
||
use crate::{ | ||
geom::{Emit, Grid, Mesh, Ray}, | ||
math::{rand_isotropic_dir, Point3}, | ||
tools::linear_to_three_dim, | ||
}; | ||
use ndarray::Array3; | ||
use rand::Rng; | ||
use std::fmt::{Display, Error, Formatter}; | ||
|
||
/// Ray emission structure. | ||
pub enum Emitter { | ||
/// Single beam. | ||
Beam(Ray), | ||
/// Points. | ||
Points(Vec<Point3>), | ||
/// Weighted points. | ||
WeightedPoints(Vec<Point3>, Vec<f64>), | ||
/// Surface mesh. | ||
Surface(Mesh), | ||
/// Volume map. | ||
Volume(Array3<f64>, Grid), | ||
} | ||
|
||
impl Emitter { | ||
/// Construct a new beam instance. | ||
#[inline] | ||
#[must_use] | ||
pub const fn new_beam(ray: Ray) -> Self { | ||
Self::Beam(ray) | ||
} | ||
|
||
/// Construct a new points instance. | ||
#[inline] | ||
#[must_use] | ||
pub fn new_points(points: Vec<Point3>) -> Self { | ||
debug_assert!(!points.is_empty()); | ||
|
||
Self::Points(points) | ||
} | ||
|
||
/// Construct a new points instance. | ||
#[inline] | ||
#[must_use] | ||
pub fn new_weighted_points(points: Vec<Point3>, weights: &[f64]) -> Self { | ||
debug_assert!(!points.is_empty()); | ||
debug_assert!(points.len() == weights.len()); | ||
|
||
let sum: f64 = weights.iter().sum(); | ||
let mut cumulative_weight = Vec::with_capacity(weights.len()); | ||
let mut total = 0.0; | ||
for w in weights { | ||
total += w; | ||
cumulative_weight.push(total / sum); | ||
} | ||
|
||
Self::WeightedPoints(points, cumulative_weight) | ||
} | ||
|
||
/// Construct a new surface instance. | ||
#[inline] | ||
#[must_use] | ||
pub const fn new_surface(mesh: Mesh) -> Self { | ||
Self::Surface(mesh) | ||
} | ||
|
||
/// Construct a new volume instance. | ||
#[inline] | ||
#[must_use] | ||
pub fn new_volume(map: Array3<f64>, grid: Grid) -> Self { | ||
debug_assert!(map.sum() > 0.0); | ||
debug_assert!(!map.is_empty()); | ||
|
||
Self::Volume(map, grid) | ||
} | ||
|
||
/// Emit a new ray. | ||
#[inline] | ||
#[must_use] | ||
pub fn emit<R: Rng>(&self, rng: &mut R) -> Ray { | ||
match *self { | ||
Self::Beam(ref ray) => ray.clone(), | ||
Self::Points(ref ps) => { | ||
Ray::new(ps[rng.gen_range(0..ps.len())], rand_isotropic_dir(rng)) | ||
} | ||
Self::WeightedPoints(ref ps, ref ws) => { | ||
let r: f64 = rng.gen(); | ||
for (p, w) in ps.iter().zip(ws) { | ||
if r <= *w { | ||
return Ray::new(*p, rand_isotropic_dir(rng)); | ||
} | ||
} | ||
unreachable!("Failed to determine weighted point to emit from."); | ||
} | ||
Self::Surface(ref mesh) => mesh.cast(rng), | ||
Self::Volume(ref map, ref grid) => { | ||
let r = rng.gen_range(0.0..map.sum()); | ||
let mut total = 0.0; | ||
for n in 0..map.len() { | ||
let index = linear_to_three_dim(n, grid.res()); | ||
total += map[index]; | ||
if total >= r { | ||
let pos = grid.gen_voxel(&index).rand_pos(rng); | ||
let dir = rand_isotropic_dir(rng); | ||
return Ray::new(pos, dir); | ||
} | ||
} | ||
panic!("Failed to emit ray from volume.") | ||
} | ||
} | ||
} | ||
} | ||
|
||
impl Display for Emitter { | ||
#[inline] | ||
fn fmt(&self, fmt: &mut Formatter) -> Result<(), Error> { | ||
let kind = match *self { | ||
Self::Beam { .. } => "Beam", | ||
Self::Points { .. } => "Points", | ||
Self::WeightedPoints { .. } => "WeightedPoints", | ||
Self::Surface { .. } => "Surface", | ||
Self::Volume { .. } => "Volume", | ||
}; | ||
write!(fmt, "{}", kind) | ||
} | ||
} |
Oops, something went wrong.