From 3cbb816d4bd92a58937afd149a12ecbe8f81a242 Mon Sep 17 00:00:00 2001 From: Nathan Vegdahl Date: Sun, 12 Feb 2017 20:29:08 -0800 Subject: [PATCH] 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* --- src/algorithm.rs | 19 ++++---- src/assembly.rs | 35 +++++++------- src/light/distant_disk_light.rs | 82 +++++++++++++++++++++++++++++++++ src/light/mod.rs | 54 +++++++++++++++++++++- src/light/sphere_light.rs | 5 +- src/main.rs | 1 + src/math/mod.rs | 4 +- src/objects_split.rs | 4 +- src/parse/data_tree.rs | 22 ++++----- src/parse/psy.rs | 80 ++++++++++++++++++++------------ src/parse/psy_light.rs | 70 ++++++++++++++++++++++++---- src/parse/psy_mesh_surface.rs | 5 +- src/renderer.rs | 46 +++++++++--------- src/scene.rs | 5 +- src/world.rs | 8 ++++ 15 files changed, 329 insertions(+), 111 deletions(-) create mode 100644 src/light/distant_disk_light.rs create mode 100644 src/world.rs diff --git a/src/algorithm.rs b/src/algorithm.rs index 1a06d4d..6347ddd 100644 --- a/src/algorithm.rs +++ b/src/algorithm.rs @@ -191,8 +191,9 @@ pub fn merge_slices_to(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 }); } diff --git a/src/assembly.rs b/src/assembly.rs index c9c44d9..7d30b25 100644 --- a/src/assembly.rs +++ b/src/assembly.rs @@ -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) diff --git a/src/light/distant_disk_light.rs b/src/light/distant_disk_light.rs new file mode 100644 index 0000000..7108a72 --- /dev/null +++ b/src/light/distant_disk_light.rs @@ -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, + directions: Vec, + colors: Vec, +} + +impl DistantDiskLight { + pub fn new(radii: Vec, directions: Vec, colors: Vec) -> 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 + } +} diff --git a/src/light/mod.rs b/src/light/mod.rs index f24f434..ea2c97a 100644 --- a/src/light/mod.rs +++ b/src/light/mod.rs @@ -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. diff --git a/src/light/sphere_light.rs b/src/light/sphere_light.rs index 4f43308..243c6da 100644 --- a/src/light/sphere_light.rs +++ b/src/light/sphere_light.rs @@ -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 { diff --git a/src/main.rs b/src/main.rs index 1e5e6aa..8595054 100644 --- a/src/main.rs +++ b/src/main.rs @@ -41,6 +41,7 @@ mod timer; mod tracer; mod transform_stack; mod triangle; +mod world; use std::fs::File; use std::io; diff --git a/src/math/mod.rs b/src/math/mod.rs index 1eec137..ebdebcc 100644 --- a/src/math/mod.rs +++ b/src/math/mod.rs @@ -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::(&i) } } diff --git a/src/objects_split.rs b/src/objects_split.rs index 2915e40..f047775 100644 --- a/src/objects_split.rs +++ b/src/objects_split.rs @@ -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(); diff --git a/src/parse/data_tree.rs b/src/parse/data_tree.rs index 3c5bfc8..c1cb739 100644 --- a/src/parse/data_tree.rs +++ b/src/parse/data_tree.rs @@ -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)); diff --git a/src/parse/psy.rs b/src/parse/psy.rs index 3446e31..fea66bd 100644 --- a/src/parse/psy.rs +++ b/src/parse/psy.rs @@ -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 { // 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 { }; 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 { -fn parse_world(tree: &DataTree) -> Result { +fn parse_world(tree: &DataTree) -> Result { if tree.is_internal() { let background_color; + let mut lights: Vec> = Vec::new(); // Parse background shader let bgs = { @@ -281,9 +284,10 @@ fn parse_world(tree: &DataTree) -> Result { 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 { }; 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 { _ => 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 { pub fn parse_matrix(contents: &str) -> Result { - 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, diff --git a/src/parse/psy_light.rs b/src/parse/psy_light.rs index 43ee716..0f3776d 100644 --- a/src/parse/psy_light.rs +++ b/src/parse/psy_light.rs @@ -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 { + 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 { if let &DataTree::Internal { ref children, .. } = tree { let mut radii = Vec::new(); @@ -32,9 +88,8 @@ pub fn parse_sphere_light(tree: &DataTree) -> Result // 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 { 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 { - 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...? diff --git a/src/parse/psy_mesh_surface.rs b/src/parse/psy_mesh_surface.rs index 6865768..713ea32 100644 --- a/src/parse/psy_mesh_surface.rs +++ b/src/parse/psy_mesh_surface.rs @@ -35,9 +35,8 @@ pub fn parse_mesh_surface(tree: &DataTree) -> Result (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; } diff --git a/src/scene.rs b/src/scene.rs index 28bd2ca..9acc906 100644 --- a/src/scene.rs +++ b/src/scene.rs @@ -1,12 +1,13 @@ use assembly::Assembly; use camera::Camera; -use color::XYZ; +use world::World; + #[derive(Debug)] pub struct Scene { pub name: Option, - pub background_color: XYZ, pub camera: Camera, + pub world: World, pub root: Assembly, } diff --git a/src/world.rs b/src/world.rs new file mode 100644 index 0000000..9b11745 --- /dev/null +++ b/src/world.rs @@ -0,0 +1,8 @@ +use color::XYZ; +use light::WorldLightSource; + +#[derive(Debug)] +pub struct World { + pub background_color: XYZ, + pub lights: Vec>, +}