Added DistantDiskLight (a.k.a. sun light) parsing and data structures.

Also created a proper World struct in the process, to store all
infinite-extent type stuff.

Note that I goofed and did a new rustfmt pass but forgot to
commit before making these changes, so there's a lot of
formatting changes in this too.  *sigh*
This commit is contained in:
Nathan Vegdahl 2017-02-12 20:29:08 -08:00
parent 746b3b0c1f
commit 3cbb816d4b
15 changed files with 329 additions and 111 deletions

View File

@ -191,8 +191,9 @@ pub fn merge_slices_to<T: Lerp + Copy, F>(slice1: &[T],
if slice1.len() == 0 || slice2.len() == 0 {
return;
} else if slice1.len() == slice2.len() {
for (xfo, (xf1, xf2)) in Iterator::zip(slice_out.iter_mut(),
Iterator::zip(slice1.iter(), slice2.iter())) {
for (xfo, (xf1, xf2)) in
Iterator::zip(slice_out.iter_mut(),
Iterator::zip(slice1.iter(), slice2.iter())) {
*xfo = merge(xf1, xf2);
}
} else if slice1.len() > slice2.len() {
@ -216,14 +217,12 @@ mod tests {
use super::*;
fn quick_select_ints(list: &mut [i32], i: usize) {
quick_select(list, i, |a, b| {
if a < b {
Ordering::Less
} else if a == b {
Ordering::Equal
} else {
Ordering::Greater
}
quick_select(list, i, |a, b| if a < b {
Ordering::Less
} else if a == b {
Ordering::Equal
} else {
Ordering::Greater
});
}

View File

@ -48,13 +48,14 @@ impl Assembly {
} else {
Matrix4x4::new()
};
if let Some((light_i, sel_pdf, whittled_n)) = self.light_accel
.select(incoming * sel_xform,
pos * sel_xform,
nor * sel_xform,
closure.as_surface_closure(),
time,
n) {
if let Some((light_i, sel_pdf, whittled_n)) =
self.light_accel
.select(incoming * sel_xform,
pos * sel_xform,
nor * sel_xform,
closure.as_surface_closure(),
time,
n) {
let inst = self.light_instances[light_i];
match inst.instance_type {
@ -239,19 +240,17 @@ impl AssemblyBuilder {
// sources.
let mut light_instances: Vec<_> = self.instances
.iter()
.filter(|inst| {
match inst.instance_type {
InstanceType::Object => {
if let Object::Light(_) = self.objects[inst.data_index] {
true
} else {
false
}
.filter(|inst| match inst.instance_type {
InstanceType::Object => {
if let Object::Light(_) = self.objects[inst.data_index] {
true
} else {
false
}
}
InstanceType::Assembly => {
self.assemblies[inst.data_index].light_accel.approximate_energy() > 0.0
}
InstanceType::Assembly => {
self.assemblies[inst.data_index].light_accel.approximate_energy() > 0.0
}
})
.map(|&a| a)

View File

@ -0,0 +1,82 @@
use std::f64::consts::PI as PI_64;
use color::{XYZ, SpectralSample, Color};
use lerp::lerp_slice;
use math::{Vector, coordinate_system_from_vector};
use sampling::{uniform_sample_cone, uniform_sample_cone_pdf};
use super::WorldLightSource;
// TODO: handle case where radius = 0.0.
#[derive(Debug)]
pub struct DistantDiskLight {
radii: Vec<f32>,
directions: Vec<Vector>,
colors: Vec<XYZ>,
}
impl DistantDiskLight {
pub fn new(radii: Vec<f32>, directions: Vec<Vector>, colors: Vec<XYZ>) -> DistantDiskLight {
DistantDiskLight {
radii: radii,
directions: directions,
colors: colors,
}
}
}
impl WorldLightSource for DistantDiskLight {
fn sample(&self, u: f32, v: f32, wavelength: f32, time: f32) -> (SpectralSample, Vector, f32) {
// Calculate time interpolated values
let radius: f64 = lerp_slice(&self.radii, time) as f64;
let direction = lerp_slice(&self.directions, time);
let col = lerp_slice(&self.colors, time);
let solid_angle_inv = 1.0 / (2.0 * PI_64 * (1.0 - radius.cos()));
// Create a coordinate system from the vector pointing at the center of
// of the light.
let (z, x, y) = coordinate_system_from_vector(-direction);
let (x, y, z) = (x.normalized(), y.normalized(), z.normalized());
// Calculate the radius in terms of cosine
let cos_theta_max: f64 = radius.cos();
// Sample the cone subtended by the light.
let sample = uniform_sample_cone(u, v, cos_theta_max).normalized();
// Calculate the final values and return everything.
let shadow_vec = (x * sample.x()) + (y * sample.y()) + (z * sample.z());
let pdf = uniform_sample_cone_pdf(cos_theta_max);
let spectral_sample = (col * solid_angle_inv as f32).to_spectral_sample(wavelength);
return (spectral_sample, shadow_vec, pdf as f32);
}
fn sample_pdf(&self, sample_dir: Vector, wavelength: f32, time: f32) -> f32 {
// We're not using these, silence warnings
let _ = (sample_dir, wavelength);
let radius: f64 = lerp_slice(&self.radii, time) as f64;
return uniform_sample_cone_pdf(radius.cos()) as f32;
}
fn outgoing(&self, dir: Vector, wavelength: f32, time: f32) -> SpectralSample {
// We're not using this, silence warning
let _ = dir;
let radius = lerp_slice(&self.radii, time) as f64;
let col = lerp_slice(&self.colors, time);
let solid_angle = 2.0 * PI_64 * (1.0 - radius.cos());
(col / solid_angle as f32).to_spectral_sample(wavelength)
}
fn is_delta(&self) -> bool {
false
}
fn approximate_energy(&self) -> f32 {
let color: XYZ = self.colors.iter().fold(XYZ::new(0.0, 0.0, 0.0), |a, &b| a + b) /
self.colors.len() as f32;
color.y
}
}

View File

@ -1,3 +1,4 @@
mod distant_disk_light;
mod rectangle_light;
mod sphere_light;
@ -7,14 +8,17 @@ use boundable::Boundable;
use color::SpectralSample;
use math::{Vector, Point, Matrix4x4};
pub use self::distant_disk_light::DistantDiskLight;
pub use self::rectangle_light::RectangleLight;
pub use self::sphere_light::SphereLight;
/// A finite light source that can be bounded in space.
pub trait LightSource: Boundable + Debug + Sync {
/// Samples the light source for a given point to be illuminated.
///
/// - arr: The point to be illuminated.
/// - space: The world-to-object space transform of the light.
/// - arr: The point to be illuminated (in world space).
/// - u: Random parameter U.
/// - v: Random parameter V.
/// - wavelength: The wavelength of light to sample at.
@ -77,6 +81,54 @@ pub trait LightSource: Boundable + Debug + Sync {
fn is_delta(&self) -> bool;
/// Returns an approximation of the total energy emitted by the light
/// source. Note that this does not need to be exact: it is used for
/// importance sampling.
fn approximate_energy(&self) -> f32;
}
/// An infinite light source that cannot be bounded in space. E.g.
/// a sun light source.
pub trait WorldLightSource: Debug + Sync {
/// Samples the light source for a given point to be illuminated.
///
/// - u: Random parameter U.
/// - v: Random parameter V.
/// - wavelength: The wavelength of light to sample at.
/// - time: The time to sample at.
///
/// Returns: The light arriving from the shadow-testing direction, the
/// vector to use for shadow testing, and the pdf of the sample.
fn sample(&self, u: f32, v: f32, wavelength: f32, time: f32) -> (SpectralSample, Vector, f32);
/// Calculates the pdf of sampling the given sample_dir. This is used
/// primarily to calculate probabilities for multiple importance sampling.
///
/// NOTE: this function CAN assume that sample_dir is a valid sample for
/// the light source (i.e. hits/lies on the light source). No guarantees
/// are made about the correctness of the return value if it isn't valid.
fn sample_pdf(&self, sample_dir: Vector, wavelength: f32, time: f32) -> f32;
/// Returns the color emitted in the given direction from the
/// given parameters on the light.
///
/// - dir: The direction of the outgoing light.
/// - wavelength: The hero wavelength of light to sample at.
/// - time: The time to sample at.
fn outgoing(&self, dir: Vector, wavelength: f32, time: f32) -> SpectralSample;
/// Returns whether the light has a delta distribution.
///
/// If a light has no chance of a ray hitting it through random process
/// then it is a delta light source. For example, point light sources,
/// lights that only emit in a single direction, etc.
fn is_delta(&self) -> bool;
/// Returns an approximation of the total energy emitted by the light
/// source. Note that this does not need to be exact: it is used for
/// importance sampling.

View File

@ -9,6 +9,7 @@ use sampling::{uniform_sample_cone, uniform_sample_cone_pdf, uniform_sample_sphe
use super::LightSource;
// TODO: handle case where radius = 0.0.
#[derive(Debug)]
pub struct SphereLight {
@ -57,7 +58,7 @@ impl LightSource for SphereLight {
// Create a coordinate system from the vector between the
// point and the center of the light
let z = pos - arr;
let d2: f64 = z.length2() as f64; // Distance from center of sphere squared
let d2: f64 = z.length2() as f64; // Distance from center of sphere squared
let d = d2.sqrt(); // Distance from center of sphere
let (z, x, y) = coordinate_system_from_vector(z);
let (x, y, z) = (x.normalized(), y.normalized(), z.normalized());
@ -125,7 +126,7 @@ impl LightSource for SphereLight {
let pos = Point::new(0.0, 0.0, 0.0);
let radius: f64 = lerp_slice(&self.radii, time) as f64;
let d2: f64 = (pos - arr).length2() as f64; // Distance from center of sphere squared
let d2: f64 = (pos - arr).length2() as f64; // Distance from center of sphere squared
let d: f64 = d2.sqrt(); // Distance from center of sphere
if d > radius {

View File

@ -41,6 +41,7 @@ mod timer;
mod tracer;
mod transform_stack;
mod triangle;
mod world;
use std::fs::File;
use std::io;

View File

@ -48,8 +48,8 @@ pub fn fast_pow2(p: f32) -> f32 {
let z: f32 = clipp - w as f32 + offset;
let i: u32 = ((1 << 23) as f32 *
(clipp + 121.2740575 + 27.7280233 / (4.84252568 - z) -
1.49012907 * z)) as u32;
(clipp + 121.2740575 + 27.7280233 / (4.84252568 - z) - 1.49012907 * z)) as
u32;
unsafe { transmute_copy::<u32, f32>(&i) }
}

View File

@ -64,8 +64,8 @@ pub fn free_sah_split<'a, T, F>(seed: u32, objects: &mut [T], bounder: &F) -> (u
// Build SAH bins
let sah_bins = {
let mut sah_bins =
[[(BBox::new(), BBox::new(), 0, 0); SAH_BIN_COUNT - 1]; SPLIT_PLANE_COUNT];
let mut sah_bins = [[(BBox::new(), BBox::new(), 0, 0); SAH_BIN_COUNT - 1];
SPLIT_PLANE_COUNT];
for obj in objects.iter() {
let tb = lerp_slice(bounder(obj), 0.5);
let centroid = tb.center().into_vector();

View File

@ -287,10 +287,10 @@ fn parse_node<'a>(source_text: (usize, &'a str)) -> ParseResult<'a> {
}
if let (Token::CloseInner, text4) = next_token(text_remaining) {
return Ok(Some((DataTree::Internal {
type_name: type_name,
ident: Some(n),
children: children,
},
type_name: type_name,
ident: Some(n),
children: children,
},
text4)));
} else {
return Err(ParseError::MissingCloseInternal(text_remaining.0));
@ -311,10 +311,10 @@ fn parse_node<'a>(source_text: (usize, &'a str)) -> ParseResult<'a> {
if let (Token::CloseInner, text3) = next_token(text_remaining) {
return Ok(Some((DataTree::Internal {
type_name: type_name,
ident: None,
children: children,
},
type_name: type_name,
ident: None,
children: children,
},
text3)));
} else {
return Err(ParseError::MissingCloseInternal(text_remaining.0));
@ -326,9 +326,9 @@ fn parse_node<'a>(source_text: (usize, &'a str)) -> ParseResult<'a> {
let (contents, text3) = parse_leaf_content(text2);
if let (Token::CloseLeaf, text4) = next_token(text3) {
return Ok(Some((DataTree::Leaf {
type_name: type_name,
contents: contents,
},
type_name: type_name,
contents: contents,
},
text4)));
} else {
return Err(ParseError::MissingCloseLeaf(text3.0));

View File

@ -9,11 +9,14 @@ use camera::Camera;
use color::{XYZ, rec709e_to_xyz};
use math::Matrix4x4;
use renderer::Renderer;
use world::World;
use scene::Scene;
use light::WorldLightSource;
use super::basics::{ws_u32, ws_f32};
use super::DataTree;
use super::psy_assembly::parse_assembly;
use super::psy_light::parse_distant_disk_light;
#[derive(Copy, Clone, Debug)]
@ -24,7 +27,6 @@ pub enum PsyParseError {
/// Takes in a DataTree representing a Scene node and returns
/// a renderer.
pub fn parse_scene(tree: &DataTree) -> Result<Renderer, PsyParseError> {
// Verify we have the right number of each section
if tree.iter_children_with_type("Output").count() != 1 {
@ -79,8 +81,8 @@ pub fn parse_scene(tree: &DataTree) -> Result<Renderer, PsyParseError> {
};
let scene = Scene {
name: scene_name,
background_color: world,
camera: camera,
world: world,
root: assembly,
};
@ -266,9 +268,10 @@ fn parse_camera(tree: &DataTree) -> Result<Camera, PsyParseError> {
fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
fn parse_world(tree: &DataTree) -> Result<World, PsyParseError> {
if tree.is_internal() {
let background_color;
let mut lights: Vec<Box<WorldLightSource>> = Vec::new();
// Parse background shader
let bgs = {
@ -281,9 +284,10 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
if bgs.iter_children_with_type("Type").count() != 1 {
return Err(PsyParseError::UnknownError);
}
if let &DataTree::Leaf { contents, .. } = bgs.iter_children_with_type("Type")
.nth(0)
.unwrap() {
if let &DataTree::Leaf { contents, .. } =
bgs.iter_children_with_type("Type")
.nth(0)
.unwrap() {
contents.trim()
} else {
return Err(PsyParseError::UnknownError);
@ -291,12 +295,12 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
};
match bgs_type {
"Color" => {
if let Some(&DataTree::Leaf { contents, .. }) = bgs.iter_children_with_type("Color")
.nth(0) {
if let IResult::Done(_, color) = closure!(tuple!(ws_f32,
ws_f32,
ws_f32))(contents.trim()
.as_bytes()) {
if let Some(&DataTree::Leaf { contents, .. }) =
bgs.iter_children_with_type("Color")
.nth(0) {
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.trim()
.as_bytes()) {
// TODO: proper color space management, not just assuming
// rec.709.
background_color = XYZ::from_tuple(rec709e_to_xyz(color));
@ -311,7 +315,22 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
_ => return Err(PsyParseError::UnknownError),
}
return Ok(background_color);
// Parse light sources
for child in tree.iter_children() {
match child {
&DataTree::Internal { type_name, .. } if type_name == "DistantDiskLight" => {
lights.push(Box::new(parse_distant_disk_light(&child)?));
}
_ => {}
}
}
// Build and return the world
return Ok(World {
background_color: background_color,
lights: lights,
});
} else {
return Err(PsyParseError::UnknownError);
}
@ -321,23 +340,24 @@ fn parse_world(tree: &DataTree) -> Result<XYZ, PsyParseError> {
pub fn parse_matrix(contents: &str) -> Result<Matrix4x4, PsyParseError> {
if let IResult::Done(_, ns) = closure!(terminated!(tuple!(ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32),
nom::eof))(contents.as_bytes()) {
if let IResult::Done(_, ns) =
closure!(terminated!(tuple!(ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32,
ws_f32),
nom::eof))(contents.as_bytes()) {
return Ok(Matrix4x4::new_from_values(ns.0,
ns.4,
ns.8,

View File

@ -4,14 +4,70 @@ use std::result::Result;
use nom::IResult;
use math::Vector;
use color::{XYZ, rec709e_to_xyz};
use light::{SphereLight, RectangleLight};
use light::{DistantDiskLight, SphereLight, RectangleLight};
use super::basics::ws_f32;
use super::DataTree;
use super::psy::PsyParseError;
pub fn parse_distant_disk_light(tree: &DataTree) -> Result<DistantDiskLight, PsyParseError> {
if let &DataTree::Internal { ref children, .. } = tree {
let mut radii = Vec::new();
let mut directions = Vec::new();
let mut colors = Vec::new();
// Parse
for child in children.iter() {
match child {
// Radius
&DataTree::Leaf { type_name, contents } if type_name == "Radius" => {
if let IResult::Done(_, radius) = ws_f32(contents.as_bytes()) {
radii.push(radius);
} else {
// Found radius, but its contents is not in the right format
return Err(PsyParseError::UnknownError);
}
}
// Direction
&DataTree::Leaf { type_name, contents } if type_name == "Direction" => {
if let IResult::Done(_, direction) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
directions.push(Vector::new(direction.0, direction.1, direction.2));
} else {
// Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError);
}
}
// Color
&DataTree::Leaf { type_name, contents } if type_name == "Color" => {
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
colors.push(XYZ::from_tuple(rec709e_to_xyz(color)));
} else {
// Found color, but its contents is not in the right format
return Err(PsyParseError::UnknownError);
}
}
_ => {}
}
}
return Ok(DistantDiskLight::new(radii, directions, colors));
} else {
return Err(PsyParseError::UnknownError);
}
}
pub fn parse_sphere_light(tree: &DataTree) -> Result<SphereLight, PsyParseError> {
if let &DataTree::Internal { ref children, .. } = tree {
let mut radii = Vec::new();
@ -32,9 +88,8 @@ pub fn parse_sphere_light(tree: &DataTree) -> Result<SphereLight, PsyParseError>
// Color
&DataTree::Leaf { type_name, contents } if type_name == "Color" => {
if let IResult::Done(_, color) = closure!(tuple!(ws_f32,
ws_f32,
ws_f32))(contents.as_bytes()) {
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?
@ -66,7 +121,7 @@ pub fn parse_rectangle_light(tree: &DataTree) -> Result<RectangleLight, PsyParse
// Dimensions
&DataTree::Leaf { type_name, contents } if type_name == "Dimensions" => {
if let IResult::Done(_, radius) =
closure!(tuple!(ws_f32, ws_f32))(contents.as_bytes()) {
closure!(tuple!(ws_f32, ws_f32))(contents.as_bytes()) {
dimensions.push(radius);
} else {
// Found dimensions, but its contents is not in the right format
@ -76,9 +131,8 @@ pub fn parse_rectangle_light(tree: &DataTree) -> Result<RectangleLight, PsyParse
// Color
&DataTree::Leaf { type_name, contents } if type_name == "Color" => {
if let IResult::Done(_, color) = closure!(tuple!(ws_f32,
ws_f32,
ws_f32))(contents.as_bytes()) {
if let IResult::Done(_, color) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(contents.as_bytes()) {
// TODO: handle color space conversions properly.
// Probably will need a special color type with its
// own parser...?

View File

@ -35,9 +35,8 @@ pub fn parse_mesh_surface(tree: &DataTree) -> Result<TriangleMesh, PsyParseError
// Collect verts for this time sample
let mut vert_count = 0;
while let IResult::Done(remaining, vert) = closure!(tuple!(ws_f32,
ws_f32,
ws_f32))(raw_text) {
while let IResult::Done(remaining, vert) =
closure!(tuple!(ws_f32, ws_f32, ws_f32))(raw_text) {
raw_text = remaining;
verts.push(Point::new(vert.0, vert.1, vert.2));

View File

@ -113,9 +113,10 @@ impl Renderer {
(halton::sample(0, offset + si as u32),
halton::sample(1, offset + si as u32)),
halton::sample(2, offset + si as u32),
map_0_1_to_wavelength(
halton::sample(3, offset + si as u32)
),
map_0_1_to_wavelength(halton::sample(3,
offset +
si as
u32)),
offset + si as u32);
paths.push(path);
rays.push(ray);
@ -249,17 +250,17 @@ impl LightPath {
lds_offset: u32)
-> (LightPath, Ray) {
(LightPath {
pixel_co: pixel_co,
lds_offset: lds_offset,
dim_offset: 6,
round: 0,
time: time,
wavelength: wavelength,
interaction: surface::SurfaceIntersection::Miss,
light_attenuation: SpectralSample::from_value(1.0, wavelength),
pending_color_addition: SpectralSample::new(wavelength),
color: SpectralSample::new(wavelength),
},
pixel_co: pixel_co,
lds_offset: lds_offset,
dim_offset: 6,
round: 0,
time: time,
wavelength: wavelength,
interaction: surface::SurfaceIntersection::Miss,
light_attenuation: SpectralSample::from_value(1.0, wavelength),
pending_color_addition: SpectralSample::new(wavelength),
color: SpectralSample::new(wavelength),
},
scene.camera.generate_ray(image_plane_co.0,
image_plane_co.1,
@ -297,13 +298,14 @@ impl LightPath {
let light_n = self.next_lds_samp();
let light_uvw = (self.next_lds_samp(), self.next_lds_samp(), self.next_lds_samp());
xform_stack.clear();
if let Some((light_color, shadow_vec, light_pdf, light_sel_pdf)) = scene.root
.sample_lights(xform_stack,
light_n,
light_uvw,
self.wavelength,
self.time,
isect) {
if let Some((light_color, shadow_vec, light_pdf, light_sel_pdf)) =
scene.root
.sample_lights(xform_stack,
light_n,
light_uvw,
self.wavelength,
self.time,
isect) {
// Calculate and store the light that will be contributed
// to the film plane if the light is not in shadow.
self.pending_color_addition = {
@ -327,7 +329,7 @@ impl LightPath {
}
} else {
// Didn't hit anything, so background color
self.color += scene.background_color.to_spectral_sample(self.wavelength) *
self.color += scene.world.background_color.to_spectral_sample(self.wavelength) *
self.light_attenuation;
return false;
}

View File

@ -1,12 +1,13 @@
use assembly::Assembly;
use camera::Camera;
use color::XYZ;
use world::World;
#[derive(Debug)]
pub struct Scene {
pub name: Option<String>,
pub background_color: XYZ,
pub camera: Camera,
pub world: World,
pub root: Assembly,
}

8
src/world.rs Normal file
View File

@ -0,0 +1,8 @@
use color::XYZ;
use light::WorldLightSource;
#[derive(Debug)]
pub struct World {
pub background_color: XYZ,
pub lights: Vec<Box<WorldLightSource>>,
}