623 lines
20 KiB
Rust
623 lines
20 KiB
Rust
use super::BasicScene;
|
|
use super::entities::*;
|
|
use crate::Arena;
|
|
use crate::spectra::get_colorspace_device;
|
|
use crate::utils::error::FileLoc;
|
|
use crate::utils::normalize_utf8;
|
|
use crate::utils::parameters::error_exit;
|
|
use crate::utils::parameters::{ParameterDictionary, ParsedParameterVector};
|
|
use crate::utils::parser::ParserTarget;
|
|
use shared::Float;
|
|
use shared::core::camera::CameraTransform;
|
|
use shared::core::geometry::Vector3f;
|
|
use shared::core::options::RenderingCoordinateSystem;
|
|
use shared::spectra::RGBColorSpace;
|
|
use shared::utils::transform;
|
|
use shared::utils::transform::{self, AnimatedTransform, Transform};
|
|
use std::collections::{HashMap, HashSet};
|
|
use std::ops::{Index, IndexMut};
|
|
use std::sync::Arc;
|
|
|
|
const MAX_TRANSFORMS: usize = 2;
|
|
|
|
#[derive(Debug, Default, Clone, Copy)]
|
|
struct TransformSet {
|
|
t: [Transform; MAX_TRANSFORMS],
|
|
}
|
|
|
|
impl TransformSet {
|
|
fn is_animated(&self) -> bool {
|
|
self.t[0] != self.t[1]
|
|
}
|
|
|
|
fn inverse(&self) -> Self {
|
|
Self {
|
|
t: [self.t[0].inverse(), self.t[1].inverse()],
|
|
}
|
|
}
|
|
}
|
|
|
|
impl Index<usize> for TransformSet {
|
|
type Output = Transform;
|
|
fn index(&self, i: usize) -> &Self::Output {
|
|
&self.t[i]
|
|
}
|
|
}
|
|
|
|
impl IndexMut<usize> for TransformSet {
|
|
fn index_mut(&mut self, i: usize) -> &mut Self::Output {
|
|
&mut self.t[i]
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Debug, Clone)]
|
|
struct GraphicsState {
|
|
pub current_inside_medium: String,
|
|
pub current_outside_medium: String,
|
|
pub current_material_name: String,
|
|
pub current_material_index: Option<usize>,
|
|
pub area_light_name: String,
|
|
pub area_light_params: ParsedParameterVector,
|
|
pub area_light_loc: FileLoc,
|
|
pub shape_attributes: ParsedParameterVector,
|
|
pub light_attributes: ParsedParameterVector,
|
|
pub material_attributes: ParsedParameterVector,
|
|
pub medium_attributes: ParsedParameterVector,
|
|
pub texture_attributes: ParsedParameterVector,
|
|
pub reverse_orientation: bool,
|
|
pub color_space: Option<Arc<RGBColorSpace>>,
|
|
pub ctm: TransformSet,
|
|
pub active_transform_bits: u32,
|
|
pub transform_start_time: Float,
|
|
pub transform_end_time: Float,
|
|
}
|
|
|
|
#[derive(PartialEq, Eq)]
|
|
enum BlockState {
|
|
OptionsBlock,
|
|
WorldBlock,
|
|
}
|
|
|
|
pub struct BasicSceneBuilder {
|
|
scene: Arc<BasicScene>,
|
|
current_block: BlockState,
|
|
graphics_state: GraphicsState,
|
|
pushed_graphics_states: Vec<GraphicsState>,
|
|
push_stack: Vec<(char, FileLoc)>,
|
|
render_from_world: Transform,
|
|
named_coordinate_systems: HashMap<String, TransformSet>,
|
|
active_instance_definition: Option<InstanceDefinitionSceneEntity>,
|
|
|
|
float_texture_names: HashSet<String>,
|
|
spectrum_texture_names: HashSet<String>,
|
|
named_material_names: HashSet<String>,
|
|
medium_names: HashSet<String>,
|
|
|
|
current_camera: Option<CameraSceneEntity>,
|
|
current_film: Option<SceneEntity>,
|
|
current_integrator: Option<SceneEntity>,
|
|
current_sampler: Option<SceneEntity>,
|
|
current_filter: Option<SceneEntity>,
|
|
current_accelerator: Option<SceneEntity>,
|
|
}
|
|
|
|
impl BasicSceneBuilder {
|
|
pub const START_TRANSFORM_BITS: u32 = 1 << 0;
|
|
pub const END_TRANSFORM_BITS: u32 = 1 << 1;
|
|
pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1;
|
|
|
|
pub fn new(scene: Arc<BasicScene>) -> Self {
|
|
Self {
|
|
scene,
|
|
current_block: BlockState::OptionsBlock,
|
|
graphics_state: GraphicsState {
|
|
active_transform_bits: Self::ALL_TRANSFORM_BITS,
|
|
..Default::default()
|
|
},
|
|
pushed_graphics_states: Vec::new(),
|
|
push_stack: Vec::new(),
|
|
render_from_world: Transform::identity(),
|
|
named_coordinate_systems: HashMap::new(),
|
|
active_instance_definition: None,
|
|
float_texture_names: HashSet::new(),
|
|
spectrum_texture_names: HashSet::new(),
|
|
named_material_names: HashSet::new(),
|
|
medium_names: HashSet::new(),
|
|
current_camera: Some(CameraSceneEntity {
|
|
base: SceneEntity {
|
|
name: "perspective".into(),
|
|
..Default::default()
|
|
},
|
|
camera_transform: CameraTransform::from_world(
|
|
AnimatedTransform::default(),
|
|
RenderingCoordinateSystem::World,
|
|
),
|
|
medium: String::new(),
|
|
}),
|
|
current_sampler: Some(SceneEntity {
|
|
name: "zsobol".into(),
|
|
..Default::default()
|
|
}),
|
|
current_filter: Some(SceneEntity {
|
|
name: "gaussian".into(),
|
|
..Default::default()
|
|
}),
|
|
current_integrator: Some(SceneEntity {
|
|
name: "volpath".into(),
|
|
..Default::default()
|
|
}),
|
|
current_accelerator: Some(SceneEntity {
|
|
name: "bvh".into(),
|
|
..Default::default()
|
|
}),
|
|
current_film: Some(SceneEntity {
|
|
name: "rgb".into(),
|
|
..Default::default()
|
|
}),
|
|
}
|
|
}
|
|
|
|
fn for_active_transforms<F>(&mut self, f: F)
|
|
where
|
|
F: Fn(&Transform) -> Transform,
|
|
{
|
|
let bits = self.graphics_state.active_transform_bits;
|
|
if (bits & 1) != 0 {
|
|
self.graphics_state.ctm.t[0] = f(&self.graphics_state.ctm.t[0]);
|
|
}
|
|
if (bits & 2) != 0 {
|
|
self.graphics_state.ctm.t[1] = f(&self.graphics_state.ctm.t[1]);
|
|
}
|
|
}
|
|
|
|
fn verify_world(&self, name: &str, loc: &FileLoc) {
|
|
if self.current_block != BlockState::WorldBlock {
|
|
log::error!("{}: {} not allowed outside WorldBlock", loc, name);
|
|
}
|
|
}
|
|
|
|
fn verify_options(&self, name: &str, loc: &FileLoc) {
|
|
if self.current_block != BlockState::OptionsBlock {
|
|
log::error!("{}: {} not allowed inside WorldBlock", loc, name);
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ParserTarget for BasicSceneBuilder {
|
|
fn reverse_orientation(&mut self, loc: FileLoc) {
|
|
self.verify_world("ReverseOrientation", &loc);
|
|
self.graphics_state.reverse_orientation = !self.graphics_state.reverse_orientation;
|
|
}
|
|
|
|
fn color_space(&mut self, name: &str, loc: FileLoc) {
|
|
let stdcs = get_colorspace_device();
|
|
let _ = match stdcs.get_named(name) {
|
|
Ok(cs) => {
|
|
self.graphics_state.color_space = unsafe { Some(Arc::new(*cs.as_ref())) };
|
|
}
|
|
Err(_) => {
|
|
eprintln!("Error: Color space '{}' unknown at {}", name, loc);
|
|
}
|
|
};
|
|
}
|
|
|
|
fn identity(&mut self, _loc: FileLoc) {
|
|
self.for_active_transforms(|_| Transform::identity());
|
|
}
|
|
|
|
fn translate(&mut self, dx: Float, dy: Float, dz: Float, _loc: FileLoc) {
|
|
let t = Transform::translate(Vector3f::new(dx, dy, dz));
|
|
self.for_active_transforms(|cur| cur * &t);
|
|
}
|
|
|
|
fn rotate(&mut self, angle: Float, ax: Float, ay: Float, az: Float, _loc: FileLoc) {
|
|
let t = Transform::rotate_around_axis(angle, Vector3f::new(ax, ay, az));
|
|
self.for_active_transforms(|cur| cur * &t);
|
|
}
|
|
|
|
fn scale(&mut self, sx: Float, sy: Float, sz: Float, _loc: FileLoc) {
|
|
let t = Transform::scale(sx, sy, sz);
|
|
self.for_active_transforms(|cur| cur * &t);
|
|
}
|
|
|
|
fn look_at(
|
|
&mut self,
|
|
ex: Float,
|
|
ey: Float,
|
|
ez: Float,
|
|
lx: Float,
|
|
ly: Float,
|
|
lz: Float,
|
|
ux: Float,
|
|
uy: Float,
|
|
uz: Float,
|
|
loc: FileLoc,
|
|
) {
|
|
let result = transform::look_at((ex, ey, ez), (lx, ly, lz), (ux, uy, uz));
|
|
match result {
|
|
Ok(t) => {
|
|
self.for_active_transforms(|cur| cur * &t);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error: {} at {}", e, loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn concat_transform(&mut self, m: &[Float; 16], loc: FileLoc) {
|
|
let result = Transform::from_flat(m);
|
|
match result {
|
|
Ok(t) => {
|
|
self.for_active_transforms(|cur| cur * &t);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error: {} at {}", e, loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn transform(&mut self, m: &[Float; 16], loc: FileLoc) {
|
|
let result = Transform::from_flat(m);
|
|
match result {
|
|
Ok(t) => {
|
|
self.for_active_transforms(|_| t);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Error: {} at {}", e, loc);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn coordinate_system(&mut self, name: &str, _loc: FileLoc) {
|
|
self.named_coordinate_systems
|
|
.insert(name.to_string(), self.graphics_state.ctm);
|
|
}
|
|
|
|
fn coord_sys_transform(&mut self, name: &str, loc: FileLoc) {
|
|
if let Some(saved_ctm) = self.named_coordinate_systems.get(name) {
|
|
self.graphics_state.ctm = *saved_ctm;
|
|
} else {
|
|
eprintln!(
|
|
"Warning: Couldn't find named coordinate system \"{}\" at {}",
|
|
name, loc
|
|
);
|
|
}
|
|
}
|
|
|
|
fn camera(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
self.verify_options("Camera", &loc);
|
|
|
|
let camera_from_world = self.graphics_state.ctm;
|
|
|
|
let world_from_camera = camera_from_world.inverse();
|
|
|
|
self.named_coordinate_systems
|
|
.insert("camera".to_string(), world_from_camera);
|
|
|
|
let animated_world_from_cam = AnimatedTransform::new(
|
|
&world_from_camera.t[0],
|
|
self.graphics_state.transform_start_time,
|
|
&world_from_camera.t[1],
|
|
self.graphics_state.transform_end_time,
|
|
);
|
|
|
|
let rendering_space = RenderingCoordinateSystem::CameraWorld;
|
|
let camera_transform =
|
|
CameraTransform::from_world(animated_world_from_cam, rendering_space);
|
|
self.render_from_world = camera_from_world.t[0];
|
|
|
|
let parameters =
|
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
|
|
|
self.current_camera = Some(CameraSceneEntity {
|
|
base: SceneEntity {
|
|
name: name.to_string(),
|
|
loc,
|
|
parameters,
|
|
},
|
|
camera_transform,
|
|
|
|
medium: self.graphics_state.current_outside_medium.clone(),
|
|
});
|
|
}
|
|
|
|
fn active_transform_all(&mut self, _loc: FileLoc) {
|
|
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
|
|
}
|
|
|
|
fn active_transform_end_time(&mut self, _loc: FileLoc) {
|
|
self.graphics_state.active_transform_bits = Self::END_TRANSFORM_BITS;
|
|
}
|
|
fn active_transform_start_time(&mut self, _loc: FileLoc) {
|
|
self.graphics_state.active_transform_bits = Self::START_TRANSFORM_BITS;
|
|
}
|
|
|
|
fn transform_times(&mut self, start: Float, end: Float, loc: FileLoc) {
|
|
self.verify_options("TransformTimes", &loc);
|
|
self.graphics_state.transform_start_time = start;
|
|
self.graphics_state.transform_end_time = end;
|
|
}
|
|
|
|
fn option(&mut self, _name: &str, _value: &str, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
|
|
fn pixel_filter(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
let parameters =
|
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
|
self.verify_options("PixelFilter", &loc);
|
|
self.current_filter = Some(SceneEntity {
|
|
name: name.to_string(),
|
|
loc,
|
|
parameters,
|
|
});
|
|
}
|
|
|
|
fn film(&mut self, type_name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
let parameters =
|
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
|
self.verify_options("Film", &loc);
|
|
self.current_filter = Some(SceneEntity {
|
|
name: type_name.to_string(),
|
|
loc,
|
|
parameters,
|
|
});
|
|
}
|
|
|
|
fn accelerator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
let parameters =
|
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
|
self.verify_options("PixelFilter", &loc);
|
|
self.current_filter = Some(SceneEntity {
|
|
name: name.to_string(),
|
|
loc,
|
|
parameters,
|
|
});
|
|
}
|
|
|
|
fn integrator(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
let parameters =
|
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
|
self.verify_options("PixelFilter", &loc);
|
|
self.current_filter = Some(SceneEntity {
|
|
name: name.to_string(),
|
|
loc,
|
|
parameters,
|
|
});
|
|
}
|
|
|
|
fn make_named_medium(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
let curr_name = normalize_utf8(name);
|
|
self.verify_world("MakeNamedMaterial", &loc);
|
|
|
|
if !self.named_material_names.insert(curr_name.to_string()) {
|
|
eprintln!("Error: {}: named material '{}' redefined.", loc, name);
|
|
return;
|
|
}
|
|
|
|
let parameters =
|
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
|
|
|
let entity = SceneEntity {
|
|
name: name.to_string(),
|
|
loc,
|
|
parameters,
|
|
};
|
|
|
|
self.scene
|
|
.named_materials
|
|
.lock()
|
|
.push((curr_name.to_string(), entity));
|
|
}
|
|
|
|
fn medium_interface(&mut self, inside_name: &str, outside_name: &str, _loc: FileLoc) {
|
|
let inside = normalize_utf8(inside_name);
|
|
let outside = normalize_utf8(outside_name);
|
|
|
|
self.graphics_state.current_inside_medium = inside;
|
|
self.graphics_state.current_outside_medium = outside;
|
|
}
|
|
|
|
fn sampler(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
let parameters =
|
|
ParameterDictionary::new(params.clone(), self.graphics_state.color_space.clone());
|
|
self.verify_options("Sampler", &loc);
|
|
self.current_sampler = Some(SceneEntity {
|
|
name: name.to_string(),
|
|
loc,
|
|
parameters,
|
|
})
|
|
}
|
|
|
|
fn world_begin(&mut self, loc: FileLoc, arena: &mut Arena) {
|
|
self.verify_options("WorldBegin", &loc);
|
|
self.current_block = BlockState::WorldBlock;
|
|
for i in 0..MAX_TRANSFORMS {
|
|
self.graphics_state.ctm[i] = Transform::default();
|
|
}
|
|
self.graphics_state.active_transform_bits = Self::ALL_TRANSFORM_BITS;
|
|
self.named_coordinate_systems
|
|
.insert("world".to_string(), self.graphics_state.ctm);
|
|
let scene = Arc::clone(&self.scene);
|
|
scene.set_options(
|
|
self.current_filter
|
|
.take()
|
|
.expect("Filter not set before WorldBegin"),
|
|
self.current_film
|
|
.take()
|
|
.expect("Film not set before WorldBegin"),
|
|
self.current_camera
|
|
.take()
|
|
.expect("Camera not set before WorldBegin"),
|
|
self.current_sampler
|
|
.take()
|
|
.expect("Sampler not set before WorldBegin"),
|
|
self.current_integrator
|
|
.take()
|
|
.expect("Integrator not set before WorldBegin"),
|
|
self.current_accelerator
|
|
.take()
|
|
.expect("Accelerator not set before WorldBegin"),
|
|
arena,
|
|
);
|
|
}
|
|
|
|
fn attribute_begin(&mut self, loc: FileLoc) {
|
|
self.verify_world("AttributeBegin", &loc);
|
|
self.pushed_graphics_states
|
|
.push(self.graphics_state.clone());
|
|
self.push_stack.push(('a', loc));
|
|
}
|
|
|
|
fn attribute_end(&mut self, loc: FileLoc) {
|
|
self.verify_world("AttributeEnd", &loc);
|
|
if self.pushed_graphics_states.is_empty() {
|
|
log::error!(
|
|
"[{:?}] Unmatched AttributeEnd encountered. Ignoring it.",
|
|
loc
|
|
);
|
|
return;
|
|
}
|
|
|
|
if let Some(state) = self.pushed_graphics_states.pop() {
|
|
self.graphics_state = state;
|
|
}
|
|
|
|
if let Some((kind, start_loc)) = self.push_stack.pop() {
|
|
if kind == 'o' {
|
|
log::error!(
|
|
"[{:?}] Mismatched nesting: open ObjectBegin from {} at AttributeEnd",
|
|
loc,
|
|
start_loc
|
|
);
|
|
std::process::exit(1);
|
|
} else {
|
|
debug_assert_eq!(kind, 'a', "Expected AttributeBegin on the stack");
|
|
}
|
|
}
|
|
}
|
|
|
|
fn attribute(&mut self, target: &str, params: ParsedParameterVector, loc: FileLoc) {
|
|
let current_attributes = match target {
|
|
"shape" => &mut self.graphics_state.shape_attributes,
|
|
"light" => &mut self.graphics_state.light_attributes,
|
|
"material" => &mut self.graphics_state.material_attributes,
|
|
"medium" => &mut self.graphics_state.medium_attributes,
|
|
"texture" => &mut self.graphics_state.texture_attributes,
|
|
_ => {
|
|
log::error!(
|
|
"[{:?}] Unknown attribute target \"{}\". Must be \"shape\", \"light\", \
|
|
\"material\", \"medium\", or \"texture\".",
|
|
loc,
|
|
target
|
|
);
|
|
return;
|
|
}
|
|
};
|
|
|
|
let active_color_space = self.graphics_state.color_space.clone();
|
|
|
|
for mut p in params {
|
|
p.may_be_unused = true;
|
|
p.color_space = active_color_space.clone();
|
|
|
|
current_attributes.push(p.clone());
|
|
}
|
|
}
|
|
|
|
fn texture(
|
|
&mut self,
|
|
orig_name: &str,
|
|
type_name: &str,
|
|
tex_name: &str,
|
|
params: &ParsedParameterVector,
|
|
loc: FileLoc,
|
|
arena: Arc<Arena>,
|
|
) {
|
|
let name = normalize_utf8(orig_name);
|
|
self.verify_world("Texture", &loc);
|
|
let dict = ParameterDictionary::from_array(
|
|
params.clone(),
|
|
&self.graphics_state.texture_attributes,
|
|
self.graphics_state.color_space.clone(),
|
|
);
|
|
|
|
if type_name != "float" && type_name != "spectrum" {
|
|
error_exit(
|
|
Some(&loc),
|
|
&format!(
|
|
"{}: texture type unknown. Must be \"float\" or \"spectrum\".",
|
|
tex_name
|
|
),
|
|
);
|
|
}
|
|
|
|
{
|
|
let names = if type_name == "float" {
|
|
&mut self.float_texture_names
|
|
} else {
|
|
&mut self.spectrum_texture_names
|
|
};
|
|
|
|
if names.contains(&name) {
|
|
error_exit(Some(&loc), &format!("Redefining texture \"{}\".", name));
|
|
}
|
|
names.insert(name.to_string());
|
|
}
|
|
|
|
let base = SceneEntity {
|
|
name: tex_name.to_string(),
|
|
parameters: dict,
|
|
loc,
|
|
};
|
|
let entity = TextureSceneEntity {
|
|
base,
|
|
render_from_object: self.graphics_state.render_from_object.clone(),
|
|
};
|
|
|
|
if type_name == "float" {
|
|
self.scene
|
|
.add_float_texture(name.to_string(), entity, arena);
|
|
} else {
|
|
self.scene
|
|
.add_spectrum_texture(name.to_string(), entity, arena);
|
|
}
|
|
}
|
|
|
|
fn material(&mut self, name: &str, params: &ParsedParameterVector, loc: FileLoc) {
|
|
self.verify_world("material", &loc);
|
|
let entity = SceneEntity {
|
|
name: name.to_string(),
|
|
loc,
|
|
parameters: ParameterDictionary::new(params.clone(), None),
|
|
};
|
|
self.graphics_state.current_material_name = self.scene.add_material(entity).to_string();
|
|
}
|
|
fn make_named_material(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn named_material(&mut self, _name: &str, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn area_light_source(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn shape(&mut self, _name: &str, _params: &ParsedParameterVector, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn object_begin(&mut self, _name: &str, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn object_end(&mut self, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn object_instance(&mut self, _name: &str, _loc: FileLoc) {
|
|
todo!()
|
|
}
|
|
fn end_of_files(&mut self) {
|
|
todo!()
|
|
}
|
|
}
|