Skip to content

Commit

Permalink
Migrated geom::cast module.
Browse files Browse the repository at this point in the history
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
sammorrell committed Jan 10, 2022
1 parent 9e02998 commit 58441a7
Show file tree
Hide file tree
Showing 8 changed files with 476 additions and 2 deletions.
41 changes: 41 additions & 0 deletions src/fs/extensions/csv.rs
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))
}
}
3 changes: 2 additions & 1 deletion src/fs/extensions/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@
//! Please see the documentation in the appropriate module for specifics on each
//! format.

pub mod csv;
pub mod json;
pub mod netcdf;
pub mod wavefront;

pub use self::{json::*, netcdf::*, wavefront::*};
pub use self::{csv::*, json::*, netcdf::*, wavefront::*};
114 changes: 114 additions & 0 deletions src/geom/cast/camera.rs
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(())
}
}
98 changes: 98 additions & 0 deletions src/geom/cast/camera_builder.rs
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(())
}
}
127 changes: 127 additions & 0 deletions src/geom/cast/emitter.rs
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)
}
}
Loading

0 comments on commit 58441a7

Please sign in to comment.