826 lines
27 KiB
Rust
826 lines
27 KiB
Rust
use super::entities::*;
|
|
use super::state::*;
|
|
use crate::core::camera::CameraFactory;
|
|
use crate::core::film::FilmFactory;
|
|
use crate::core::filter::FilterFactory;
|
|
use crate::core::image::{Image, io::ImageIO};
|
|
use crate::core::light::LightFactory;
|
|
use crate::core::material::MaterialFactory;
|
|
use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive};
|
|
use crate::core::sampler::SamplerFactory;
|
|
use crate::core::shape::ShapeFactory;
|
|
use crate::core::texture::{FloatTexture, SpectrumTexture};
|
|
use crate::utils::parallel::{AsyncJob, run_async};
|
|
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
|
|
use crate::utils::{Upload, resolve_filename};
|
|
use crate::{Arena, FileLoc};
|
|
use anyhow::{Result, anyhow};
|
|
use parking_lot::Mutex;
|
|
use rayon::prelude::*;
|
|
use shared::core::camera::{CameraTransform, Camera};
|
|
use shared::core::color::LINEAR;
|
|
use shared::core::film::Film;
|
|
use shared::core::filter::Filter;
|
|
use shared::core::light::Light;
|
|
use shared::core::material::Material;
|
|
use shared::core::medium::{Medium, MediumInterface};
|
|
use shared::core::primitive::{GeometricPrimitive, Primitive, SimplePrimitive};
|
|
use shared::core::sampler::Sampler;
|
|
use shared::core::shape::Shape;
|
|
use shared::core::texture::SpectrumType;
|
|
use shared::spectra::RGBColorSpace;
|
|
use shared::utils::Ptr;
|
|
use std::collections::HashMap;
|
|
use std::sync::Arc;
|
|
|
|
pub struct SceneLookup<'a> {
|
|
pub textures: &'a NamedTextures,
|
|
pub media: &'a HashMap<String, Arc<Medium>>,
|
|
pub named_materials: &'a HashMap<String, Material>,
|
|
pub materials: &'a Vec<Material>,
|
|
pub shape_lights: &'a HashMap<usize, Vec<Light>>,
|
|
}
|
|
|
|
impl<'a> SceneLookup<'a> {
|
|
pub fn find_medium(&self, name: &str, loc: &FileLoc) -> Option<Arc<Medium>> {
|
|
if name.is_empty() {
|
|
return None;
|
|
}
|
|
self.media.get(name).cloned().or_else(|| {
|
|
panic!("{}: medium '{}' not defined", loc, name);
|
|
})
|
|
}
|
|
|
|
pub fn resolve_material(&self, mat_ref: &MaterialRef, _loc: &FileLoc) -> Option<Material> {
|
|
match mat_ref {
|
|
MaterialRef::Name(name) => {
|
|
Some(*self.named_materials.get(name).expect("Material not found"))
|
|
}
|
|
MaterialRef::Index(idx) => Some(self.materials[*idx]),
|
|
MaterialRef::None => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct BasicScene {
|
|
pub integrator: Mutex<Option<SceneEntity>>,
|
|
pub accelerator: Mutex<Option<SceneEntity>>,
|
|
pub film_colorspace: Mutex<Option<Arc<RGBColorSpace>>>,
|
|
|
|
pub shapes: Mutex<Vec<ShapeSceneEntity>>,
|
|
pub animated_shapes: Mutex<Vec<AnimatedShapeSceneEntity>>,
|
|
|
|
pub instances: Mutex<Vec<InstanceSceneEntity>>,
|
|
pub instance_definitions: Mutex<HashMap<String, Arc<InstanceDefinitionSceneEntity>>>,
|
|
|
|
pub media_state: Mutex<MediaState>,
|
|
pub material_state: Mutex<MaterialState>,
|
|
pub light_state: Mutex<LightState>,
|
|
pub texture_state: Mutex<TextureState>,
|
|
|
|
pub camera_state: Mutex<SingletonState<Camera>>,
|
|
pub sampler_state: Mutex<SingletonState<Sampler>>,
|
|
pub film_state: Mutex<SingletonState<Film>>,
|
|
}
|
|
|
|
impl BasicScene {
|
|
pub fn new() -> Self {
|
|
Self {
|
|
integrator: Mutex::new(None),
|
|
accelerator: Mutex::new(None),
|
|
film_colorspace: Mutex::new(None),
|
|
shapes: Mutex::new(Vec::new()),
|
|
animated_shapes: Mutex::new(Vec::new()),
|
|
instances: Mutex::new(Vec::new()),
|
|
instance_definitions: Mutex::new(HashMap::new()),
|
|
media_state: Mutex::new(MediaState::default()),
|
|
material_state: Mutex::new(MaterialState::default()),
|
|
light_state: Mutex::new(LightState::default()),
|
|
texture_state: Mutex::new(TextureState::default()),
|
|
camera_state: Mutex::new(SingletonState::default()),
|
|
sampler_state: Mutex::new(SingletonState::default()),
|
|
film_state: Mutex::new(SingletonState::default()),
|
|
}
|
|
}
|
|
|
|
pub fn set_options(
|
|
self: &Arc<Self>,
|
|
filter: SceneEntity,
|
|
film: SceneEntity,
|
|
camera: CameraSceneEntity,
|
|
sampler: SceneEntity,
|
|
integ: SceneEntity,
|
|
accel: SceneEntity,
|
|
arena: Arc<Arena>,
|
|
) -> Result<()> {
|
|
*self.integrator.lock() = Some(integ);
|
|
*self.accelerator.lock() = Some(accel);
|
|
|
|
if let Some(cs) = film.parameters.color_space.as_ref() {
|
|
*self.film_colorspace.lock() = Some(Arc::clone(cs));
|
|
}
|
|
|
|
let filter = Filter::create(&filter.name, &filter.parameters, &filter.loc)
|
|
.map_err(|e| anyhow!("Failed to create filter: {}", e))?;
|
|
let shutter_close = camera.base.parameters.get_one_float("shutterclose", 1.)?;
|
|
let shutter_open = camera.base.parameters.get_one_float("shutteropen", 0.)?;
|
|
let exposure_time = shutter_close - shutter_open;
|
|
|
|
let film_instance = Arc::new(
|
|
Film::create(
|
|
&film.name,
|
|
&film.parameters,
|
|
exposure_time,
|
|
filter,
|
|
Some(camera.camera_transform.clone()),
|
|
&film.loc,
|
|
&arena,
|
|
)
|
|
.map_err(|e| anyhow!("Failed to create film: {}", e))?,
|
|
);
|
|
|
|
*self.film_state.lock() = SingletonState {
|
|
result: Some(Arc::clone(&film_instance)),
|
|
job: None,
|
|
};
|
|
|
|
let arena_sampler = Arc::clone(&arena);
|
|
let sampler_film = Arc::clone(&film_instance);
|
|
let sampler_job = run_async(move || {
|
|
let res = sampler_film.as_ref().base().full_resolution;
|
|
Sampler::create(
|
|
&sampler.name,
|
|
&sampler.parameters,
|
|
res,
|
|
&sampler.loc,
|
|
&arena_sampler,
|
|
)
|
|
.map_err(|e| anyhow!("Failed to create sampler: {}", e))
|
|
});
|
|
self.sampler_state.lock().job = Some(sampler_job);
|
|
|
|
let arena_camera = Arc::clone(&arena);
|
|
let camera_film = Arc::clone(&film_instance);
|
|
let scene_ptr = Arc::clone(self);
|
|
let camera_job = run_async(move || {
|
|
let medium = scene_ptr.get_medium(&camera.medium, &camera.base.loc);
|
|
Camera::create(
|
|
&camera.base.name,
|
|
&camera.base.parameters,
|
|
&camera.camera_transform,
|
|
*medium.unwrap(),
|
|
camera_film,
|
|
&camera.base.loc,
|
|
&arena_camera,
|
|
)
|
|
.map_err(|e| anyhow!("Failed to create camera: {}", e))
|
|
});
|
|
self.camera_state.lock().job = Some(camera_job);
|
|
Ok(())
|
|
}
|
|
|
|
pub fn add_named_material(&self, name: &str, material: SceneEntity) {
|
|
let mut state = self.material_state.lock();
|
|
self.start_loading_normal_maps(&mut state, &material.parameters);
|
|
state.named_materials.push((name.to_string(), material));
|
|
}
|
|
|
|
pub fn add_material(&self, material: SceneEntity) -> usize {
|
|
let mut state = self.material_state.lock();
|
|
self.start_loading_normal_maps(&mut state, &material.parameters);
|
|
state.materials.push(material);
|
|
state.materials.len() - 1
|
|
}
|
|
|
|
fn add_texture_generic<T, F>(
|
|
&self,
|
|
name: String,
|
|
texture: TextureSceneEntity,
|
|
state: &mut TextureState,
|
|
get_serial: impl FnOnce(&mut TextureState) -> &mut Vec<(String, TextureSceneEntity)>,
|
|
get_jobs: impl FnOnce(&mut TextureState) -> &mut HashMap<String, AsyncJob<Arc<T>>>,
|
|
create_fn: F,
|
|
) -> Result<()>
|
|
where
|
|
T: Send + Sync + 'static,
|
|
F: FnOnce(TextureSceneEntity) -> T + Send + 'static,
|
|
{
|
|
if texture.render_from_object.is_animated() {
|
|
log::info!(
|
|
"{}: Animated world to texture not supported, using start",
|
|
texture.base.loc
|
|
);
|
|
}
|
|
|
|
if texture.base.name != "imagemap" && texture.base.name != "ptex" {
|
|
get_serial(state).push((name, texture));
|
|
return Ok(());
|
|
}
|
|
|
|
let filename = resolve_filename(&texture.base.parameters.get_one_string("filename", "")?);
|
|
if !self.validate_texture_file(&filename, &texture.base.loc, &mut state.n_missing_textures)
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
if state.loading_texture_filenames.contains(&filename) {
|
|
get_serial(state).push((name, texture));
|
|
return Ok(());
|
|
}
|
|
|
|
state.loading_texture_filenames.insert(filename);
|
|
let job = run_async(move || Arc::new(create_fn(texture)));
|
|
get_jobs(state).insert(name, job);
|
|
Ok(())
|
|
}
|
|
|
|
fn validate_texture_file(&self, filename: &str, loc: &FileLoc, n_missing: &mut usize) -> bool {
|
|
if filename.is_empty() {
|
|
log::error!(
|
|
"[{:?}] \"string filename\" not provided for image texture.",
|
|
loc
|
|
);
|
|
*n_missing += 1;
|
|
return false;
|
|
}
|
|
if !std::path::Path::new(filename).exists() {
|
|
log::error!("[{:?}] {}: file not found.", loc, filename);
|
|
*n_missing += 1;
|
|
return false;
|
|
}
|
|
true
|
|
}
|
|
|
|
pub fn add_float_texture(
|
|
&self,
|
|
name: String,
|
|
texture: TextureSceneEntity,
|
|
arena: Arc<Arena>,
|
|
) -> Result<()> {
|
|
let mut state = self.texture_state.lock();
|
|
self.add_texture_generic(
|
|
name,
|
|
texture,
|
|
&mut state,
|
|
|s| &mut s.serial_float_textures,
|
|
|s| &mut s.float_texture_jobs,
|
|
move |tex| {
|
|
let render_from_texture = tex.render_from_object.start_transform;
|
|
let tex_dict = TextureParameterDictionary::new(tex.base.parameters.into(), None);
|
|
FloatTexture::create(
|
|
&tex.base.name,
|
|
render_from_texture,
|
|
tex_dict,
|
|
tex.base.loc,
|
|
&arena,
|
|
)
|
|
.expect("Could not create Float texture")
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn add_spectrum_texture(
|
|
&self,
|
|
name: String,
|
|
texture: TextureSceneEntity,
|
|
arena: Arc<Arena>,
|
|
) -> Result<()> {
|
|
let mut state = self.texture_state.lock();
|
|
self.add_texture_generic(
|
|
name,
|
|
texture,
|
|
&mut state,
|
|
|s| &mut s.serial_spectrum_textures,
|
|
|s| &mut s.spectrum_texture_jobs,
|
|
move |tex| {
|
|
let render_from_texture = tex.render_from_object.start_transform;
|
|
let tex_dict = TextureParameterDictionary::new(tex.base.parameters.into(), None);
|
|
SpectrumTexture::create(
|
|
&tex.base.name,
|
|
render_from_texture,
|
|
tex_dict,
|
|
SpectrumType::Albedo,
|
|
tex.base.loc,
|
|
&arena,
|
|
)
|
|
.expect("Could not create spectrum texture")
|
|
},
|
|
)
|
|
}
|
|
|
|
pub fn add_area_light(&self, light: SceneEntity) -> usize {
|
|
let mut state = self.light_state.lock();
|
|
state.area_lights.push(light);
|
|
state.area_lights.len() - 1
|
|
}
|
|
|
|
pub fn add_light(&self, light: LightSceneEntity) {
|
|
self.light_state.lock().lights.push(light);
|
|
}
|
|
|
|
pub fn add_shape(&self, shape: ShapeSceneEntity) {
|
|
self.shapes.lock().push(shape);
|
|
}
|
|
|
|
pub fn add_shapes(&self, new_shapes: Vec<ShapeSceneEntity>) {
|
|
self.shapes.lock().extend(new_shapes);
|
|
}
|
|
|
|
pub fn add_animated_shapes(&self, new_shapes: Vec<AnimatedShapeSceneEntity>) {
|
|
self.animated_shapes.lock().extend(new_shapes);
|
|
}
|
|
|
|
pub fn add_instance_definition(&self, instance: InstanceDefinitionSceneEntity) {
|
|
let name = instance.name.clone();
|
|
self.instance_definitions
|
|
.lock()
|
|
.insert(name, Arc::new(instance));
|
|
}
|
|
|
|
pub fn add_instance_uses(&self, uses: Vec<InstanceSceneEntity>) {
|
|
self.instances.lock().extend(uses);
|
|
}
|
|
|
|
pub fn create_textures(&self, arena: &mut Arena) -> NamedTextures {
|
|
let mut state = self.texture_state.lock();
|
|
|
|
let mut float_textures: HashMap<String, Arc<FloatTexture>> = HashMap::new();
|
|
let mut spectrum_textures: HashMap<String, Arc<SpectrumTexture>> = HashMap::new();
|
|
|
|
// Collect async jobs
|
|
for (name, job) in state.float_texture_jobs.drain() {
|
|
float_textures.insert(name, job.wait());
|
|
}
|
|
|
|
for (name, job) in state.spectrum_texture_jobs.drain() {
|
|
spectrum_textures.insert(name, job.wait());
|
|
}
|
|
|
|
// Create serial textures (need access to loaded textures, using shared memory)
|
|
let mut named = NamedTextures {
|
|
float_textures: Arc::new(float_textures.clone()),
|
|
albedo_spectrum_textures: Arc::new(spectrum_textures.clone()),
|
|
illuminant_spectrum_textures: Arc::new(spectrum_textures.clone()),
|
|
unbounded_spectrum_textures: Arc::new(spectrum_textures.clone()),
|
|
};
|
|
|
|
for (name, entity) in state.serial_float_textures.drain(..) {
|
|
let render_from_texture = entity.render_from_object.start_transform;
|
|
let tex_dict =
|
|
TextureParameterDictionary::new(entity.base.parameters.into(), Some(&named));
|
|
let tex = FloatTexture::create(
|
|
&entity.base.name,
|
|
render_from_texture,
|
|
tex_dict,
|
|
entity.base.loc,
|
|
arena,
|
|
)
|
|
.expect("Could not create float texture");
|
|
Arc::make_mut(&mut named.float_textures).insert(name, Arc::new(tex));
|
|
}
|
|
|
|
for (name, entity) in state.serial_spectrum_textures.drain(..) {
|
|
let render_from_texture = entity.render_from_object.start_transform;
|
|
let tex_dict =
|
|
TextureParameterDictionary::new(entity.base.parameters.into(), Some(&named));
|
|
let tex = SpectrumTexture::create(
|
|
&entity.base.name,
|
|
render_from_texture,
|
|
tex_dict,
|
|
SpectrumType::Albedo,
|
|
entity.base.loc,
|
|
arena,
|
|
)
|
|
.expect("Could not create spectrum texture");
|
|
Arc::make_mut(&mut named.albedo_spectrum_textures).insert(name, Arc::new(tex));
|
|
}
|
|
named
|
|
}
|
|
|
|
// Assuming that we can carry on if a material is missing.
|
|
// This might be a bad idea, but testing it for now (2026/02/19)
|
|
pub fn create_materials(
|
|
&self,
|
|
textures: &NamedTextures,
|
|
arena: &mut Arena,
|
|
) -> Result<(HashMap<String, Material>, Vec<Material>)> {
|
|
let mut state = self.material_state.lock();
|
|
|
|
let finished: Vec<_> = state.normal_map_jobs.drain().collect();
|
|
for (filename, job) in finished {
|
|
match std::panic::catch_unwind(|| job.wait()) {
|
|
Ok(img) => {
|
|
state.normal_maps.insert(filename, img);
|
|
}
|
|
Err(_) => {
|
|
log::error!("Failed to load normal map: {}", filename);
|
|
}
|
|
}
|
|
}
|
|
|
|
let mut named_materials: HashMap<String, Material> = HashMap::new();
|
|
|
|
for (name, entity) in &state.named_materials {
|
|
if named_materials.contains_key(name) {
|
|
log::error!(
|
|
"{}: trying to redefine named material '{}'.",
|
|
entity.loc,
|
|
name
|
|
);
|
|
continue;
|
|
}
|
|
|
|
let mat_type = entity.parameters.get_one_string("type", "")?;
|
|
if mat_type.is_empty() {
|
|
log::error!("{}: missing material type", entity.loc);
|
|
continue;
|
|
}
|
|
|
|
let normal_map = self.get_normal_map(&state, &entity.parameters)?;
|
|
let tex_dict = TextureParameterDictionary::new(
|
|
Arc::new(entity.parameters.clone()),
|
|
Some(textures),
|
|
);
|
|
|
|
match Material::create(
|
|
&mat_type,
|
|
&tex_dict,
|
|
normal_map,
|
|
&named_materials,
|
|
entity.loc.clone(),
|
|
arena,
|
|
) {
|
|
Ok(mat) => {
|
|
named_materials.insert(name.clone(), mat);
|
|
}
|
|
Err(e) => {
|
|
log::error!(
|
|
"{}: Failed to create material '{}': {}",
|
|
entity.loc,
|
|
name,
|
|
e
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
let materials: Vec<Material> = state
|
|
.materials
|
|
.iter()
|
|
.filter_map(|entity| {
|
|
let result: Result<Material> = (|| {
|
|
let normal_map = self.get_normal_map(&state, &entity.parameters)?;
|
|
let tex_dict = TextureParameterDictionary::new(
|
|
entity.parameters.clone().into(),
|
|
Some(textures),
|
|
);
|
|
|
|
Material::create(
|
|
&entity.name,
|
|
&tex_dict,
|
|
normal_map,
|
|
&named_materials,
|
|
entity.loc.clone(),
|
|
arena,
|
|
)
|
|
})();
|
|
|
|
match result {
|
|
Ok(mat) => Some(mat),
|
|
Err(e) => {
|
|
log::error!("{}: Failed to create material: {}", entity.loc, e);
|
|
None
|
|
}
|
|
}
|
|
})
|
|
.collect();
|
|
|
|
Ok((named_materials, materials))
|
|
}
|
|
|
|
pub fn create_lights(
|
|
&self,
|
|
camera_transform: &CameraTransform,
|
|
arena: &mut Arena,
|
|
) -> Result<Vec<Light>> {
|
|
let state = self.light_state.lock();
|
|
|
|
state.lights.par_iter().map(|entity| {
|
|
let render_from_light = entity.transformed_base.render_from_object.start_transform;
|
|
let medium = self.get_medium(
|
|
&entity.medium,
|
|
&entity.transformed_base.base.loc,
|
|
);
|
|
|
|
Light::create(
|
|
&entity.transformed_base.base.name,
|
|
&entity.transformed_base.base.parameters,
|
|
render_from_light,
|
|
camera_transform,
|
|
medium.map(|m| *m),
|
|
&entity.transformed_base.base.loc,
|
|
arena,
|
|
)
|
|
}).collect()
|
|
}
|
|
|
|
pub fn create_aggregate(
|
|
&self,
|
|
textures: &NamedTextures,
|
|
named_materials: &HashMap<String, Material>,
|
|
materials: &Vec<Material>,
|
|
shape_lights: &HashMap<usize, Vec<Light>>,
|
|
arena: &mut Arena,
|
|
) -> Vec<Primitive> {
|
|
let shapes = self.shapes.lock();
|
|
let animated_shapes = self.animated_shapes.lock();
|
|
let media = self.media_state.lock();
|
|
|
|
let lookup = SceneLookup {
|
|
textures,
|
|
media: &media.map,
|
|
named_materials,
|
|
materials,
|
|
shape_lights,
|
|
};
|
|
|
|
let mut primitives = Vec::new();
|
|
|
|
let loaded = self.load_shapes_parallel(&shapes, &lookup, arena);
|
|
primitives.extend(self.upload_shapes(arena, &shapes, loaded, &lookup));
|
|
|
|
let loaded_anim = self.load_animated_shapes_parallel(&animated_shapes, &lookup, arena);
|
|
primitives.extend(self.upload_animated_shapes(
|
|
arena,
|
|
&animated_shapes,
|
|
loaded_anim,
|
|
&lookup,
|
|
));
|
|
|
|
primitives
|
|
}
|
|
|
|
fn load_shapes_parallel(
|
|
&self,
|
|
entities: &[ShapeSceneEntity],
|
|
lookup: &SceneLookup,
|
|
arena: &mut Arena,
|
|
) -> Vec<Vec<Shape>> {
|
|
entities
|
|
.par_iter()
|
|
.filter_map(|sh| {
|
|
Shape::create(
|
|
&sh.base.name,
|
|
*sh.render_from_object.as_ref(),
|
|
*sh.object_from_render.as_ref(),
|
|
sh.reverse_orientation,
|
|
sh.base.parameters.clone(),
|
|
&lookup.textures.float_textures,
|
|
sh.base.loc.clone(),
|
|
arena,
|
|
)
|
|
.ok()
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn load_animated_shapes_parallel(
|
|
&self,
|
|
entities: &[AnimatedShapeSceneEntity],
|
|
lookup: &SceneLookup,
|
|
arena: &Arena,
|
|
) -> Vec<Vec<Shape>> {
|
|
entities
|
|
.par_iter()
|
|
.map(|sh| {
|
|
Shape::create(
|
|
&sh.transformed_base.base.name,
|
|
*sh.identity.as_ref(),
|
|
*sh.identity.as_ref(),
|
|
sh.reverse_orientation,
|
|
sh.transformed_base.base.parameters.clone(),
|
|
&lookup.textures.float_textures,
|
|
sh.transformed_base.base.loc.clone(),
|
|
arena,
|
|
)
|
|
.expect("Could not create shape")
|
|
})
|
|
.collect()
|
|
}
|
|
|
|
fn upload_shapes(
|
|
&self,
|
|
arena: &Arena,
|
|
entities: &[ShapeSceneEntity],
|
|
loaded: Vec<Vec<Shape>>,
|
|
lookup: &SceneLookup,
|
|
) -> Vec<Primitive> {
|
|
let mut primitives = Vec::new();
|
|
|
|
for (i, (entity, shapes)) in entities.iter().zip(loaded).enumerate() {
|
|
if shapes.is_empty() {
|
|
continue;
|
|
}
|
|
|
|
let alpha_tex = self.get_alpha_texture(
|
|
&entity.base.parameters,
|
|
&entity.base.loc,
|
|
&lookup.textures.float_textures,
|
|
);
|
|
|
|
let mtl = lookup
|
|
.resolve_material(&entity.material, &entity.base.loc)
|
|
.unwrap();
|
|
|
|
let mi = MediumInterface::new(
|
|
lookup
|
|
.find_medium(&entity.inside_medium, &entity.base.loc)
|
|
.unwrap()
|
|
.as_ref(),
|
|
lookup
|
|
.find_medium(&entity.outside_medium, &entity.base.loc)
|
|
.unwrap()
|
|
.as_ref(),
|
|
);
|
|
|
|
let shape_lights_opt = lookup.shape_lights.get(&i);
|
|
|
|
for (j, shape) in shapes.into_iter().enumerate() {
|
|
let mut area_light = None;
|
|
if entity.light_index.is_some() {
|
|
if let Some(lights) = shape_lights_opt {
|
|
if j < lights.len() {
|
|
area_light = Some(lights[j].clone());
|
|
}
|
|
}
|
|
}
|
|
|
|
let shape_ptr = shape.upload(arena);
|
|
|
|
let prim =
|
|
if area_light.is_none() && !mi.is_medium_transition() && alpha_tex.is_none() {
|
|
let p = SimplePrimitive::new(shape_ptr, Ptr::from(&mtl));
|
|
Primitive::Simple(p)
|
|
} else {
|
|
let p = GeometricPrimitive::new(
|
|
shape_ptr,
|
|
mtl.upload(arena),
|
|
area_light.upload(arena),
|
|
mi.clone(),
|
|
alpha_tex.upload(arena),
|
|
);
|
|
Primitive::Geometric(p)
|
|
};
|
|
|
|
primitives.push(prim);
|
|
}
|
|
}
|
|
|
|
primitives
|
|
}
|
|
|
|
fn upload_animated_shapes(
|
|
&self,
|
|
_arena: &mut Arena,
|
|
_entities: &[AnimatedShapeSceneEntity],
|
|
_loaded: Vec<Vec<Shape>>,
|
|
_lookup: &SceneLookup,
|
|
) -> Vec<Primitive> {
|
|
// TODO: implement animated shape upload
|
|
Vec::new()
|
|
}
|
|
|
|
// Getters
|
|
|
|
pub fn get_camera(&self) -> Result<Arc<Camera>> {
|
|
self.get_singleton(&self.camera_state, "Camera")
|
|
}
|
|
|
|
pub fn get_sampler(&self) -> Result<Arc<Sampler>> {
|
|
self.get_singleton(&self.sampler_state, "Sampler")
|
|
}
|
|
|
|
pub fn get_film(&self) -> Result<Arc<Film>> {
|
|
self.get_singleton(&self.film_state, "Film")
|
|
}
|
|
|
|
// Helpers
|
|
|
|
fn get_singleton<T: Send + 'static>(
|
|
&self,
|
|
state: &Mutex<SingletonState<T>>,
|
|
name: &str,
|
|
) -> Result<Arc<T>> {
|
|
let mut guard = state.lock();
|
|
|
|
if let Some(ref res) = guard.result {
|
|
return Ok(res.clone());
|
|
}
|
|
|
|
if let Some(job) = guard.job.take() {
|
|
let val = job.wait()?;
|
|
let res = Arc::new(val);
|
|
guard.result = Some(res.clone());
|
|
return Ok(res);
|
|
}
|
|
|
|
Err(anyhow!("{} requested but not initialized!", name))
|
|
}
|
|
|
|
fn start_loading_normal_maps(
|
|
&self,
|
|
state: &mut MaterialState,
|
|
params: &ParameterDictionary,
|
|
) -> Result<()> {
|
|
let filename = resolve_filename(¶ms.get_one_string("normalmap", "")?);
|
|
if filename.is_empty() {
|
|
return Ok(());
|
|
}
|
|
|
|
if state.normal_map_jobs.contains_key(&filename)
|
|
|| state.normal_maps.contains_key(&filename)
|
|
{
|
|
return Ok(());
|
|
}
|
|
|
|
let filename_clone = filename.clone();
|
|
let job = run_async(move || {
|
|
let path = std::path::Path::new(&filename_clone);
|
|
let immeta = Image::read(path, Some(LINEAR)).expect(&format!(
|
|
"{}: normal map must contain R, G, B channels",
|
|
filename_clone
|
|
));
|
|
|
|
let rgb_desc = immeta
|
|
.image
|
|
.get_channel_desc(&["R", "G", "B"])
|
|
.expect(&format!(
|
|
"{}: normal map must contain R, G, B channels",
|
|
filename_clone
|
|
));
|
|
|
|
Arc::new(immeta.image.select_channels(&rgb_desc))
|
|
});
|
|
|
|
state.normal_map_jobs.insert(filename, job);
|
|
Ok(())
|
|
}
|
|
|
|
fn get_normal_map(
|
|
&self,
|
|
state: &MaterialState,
|
|
params: &ParameterDictionary,
|
|
) -> Result<Option<Arc<Image>>> {
|
|
let filename = resolve_filename(¶ms.get_one_string("normalmap", "")?);
|
|
if filename.is_empty() {
|
|
return Ok(None);
|
|
}
|
|
Ok(state.normal_maps.get(&filename).cloned())
|
|
}
|
|
|
|
fn get_alpha_texture(
|
|
&self,
|
|
params: &ParameterDictionary,
|
|
loc: &FileLoc,
|
|
textures: &HashMap<String, Arc<FloatTexture>>,
|
|
) -> Option<Arc<FloatTexture>> {
|
|
let name = params.get_texture("alpha");
|
|
match textures.get(&name) {
|
|
Some(tex) => Some(tex.clone()),
|
|
None => panic!("{:?}: Alpha texture '{}' not found", loc, name),
|
|
}
|
|
// } else {
|
|
// let alpha_val = params.get_one_float("alpha", 1.0);
|
|
// if alpha_val < 1.0 {
|
|
// Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new(
|
|
// alpha_val,
|
|
// ))))
|
|
// } else {
|
|
// None
|
|
// }
|
|
// }
|
|
}
|
|
|
|
pub fn get_medium(&self, name: &str, loc: &FileLoc) -> Option<Arc<Medium>> {
|
|
if name.is_empty() {
|
|
return None;
|
|
}
|
|
|
|
let mut state = self.media_state.lock();
|
|
|
|
if let Some(medium) = state.map.get(name) {
|
|
return Some(Arc::clone(medium));
|
|
}
|
|
|
|
if let Some(job) = state.jobs.remove(name) {
|
|
let job: AsyncJob<Medium> = job;
|
|
let result: Medium = job.wait();
|
|
let medium: Arc<Medium> = Arc::new(result);
|
|
state.map.insert(name.to_string(), medium.clone());
|
|
return Some(medium);
|
|
}
|
|
|
|
log::error!("{}: Medium \"{}\" is not defined.", loc, name);
|
|
None
|
|
}
|
|
}
|