pbrt/src/core/scene/scene.rs

1096 lines
37 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::{HostImage, ImageIO};
use crate::core::material::MaterialFactory;
use crate::core::primitive::{CreateGeometricPrimitive, CreateSimplePrimitive};
use crate::core::sampler::SamplerFactory;
use crate::core::shape::{ShapeFactory, ShapeWithContext};
use crate::core::texture::{FloatTexture, SpectrumTexture};
use crate::utils::parallel::{run_async, AsyncJob};
use crate::utils::parameters::{NamedTextures, ParameterDictionary, TextureParameterDictionary};
use crate::utils::resolve_filename;
use crate::{Arena, ArenaUpload, FileLoc};
use anyhow::{anyhow, Result};
use parking_lot::Mutex;
use shared::core::camera::{Camera, CameraTransform};
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::{AnimatedPrimitive, GeometricPrimitive, Primitive, SimplePrimitive};
use shared::core::sampler::Sampler;
use shared::core::shape::Shape;
use shared::core::texture::{GPUFloatTexture, SpectrumType};
use shared::spectra::RGBColorSpace;
use shared::{Ptr, Transform};
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<Ptr<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(|| {
log::error!("{}: medium '{}' not defined", loc, name);
None
})
}
pub fn resolve_material(&self, mat_ref: &MaterialRef, loc: &FileLoc) -> Option<Material> {
match mat_ref {
MaterialRef::Name(name) => match self.named_materials.get(name) {
Some(m) => Some(*m),
None => {
log::error!("{}: named material '{}' not found", loc, name);
None
}
},
MaterialRef::Index(idx) => {
if *idx < self.materials.len() {
Some(self.materials[*idx])
} else {
log::error!("{}: material index {} out of bounds", loc, idx);
None
}
}
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: &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, &arena)
.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 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,
)
.map_err(|e| anyhow!("Failed to create sampler: {}", e))
});
self.sampler_state.lock().job = Some(sampler_job);
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,
camera_film,
&camera.base.loc,
&arena,
)
.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() {
eprintln!(
"[{:?}] \"string filename\" not provided for image texture.",
loc
);
*n_missing += 1;
return false;
}
if !std::path::Path::new(filename).exists() {
eprintln!("[{:?}] {}: 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,
) -> Vec<Light> {
let state = self.light_state.lock();
state
.lights
.iter()
.filter_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)
.map(|m| *m);
match crate::core::light::create_light(
&entity.transformed_base.base.name,
render_from_light,
medium,
&entity.transformed_base.base.parameters,
&entity.transformed_base.base.loc,
camera_transform.clone(),
arena,
) {
Ok(light) => Some(light),
Err(e) => {
log::error!(
"{}: failed to create light: {}",
entity.transformed_base.base.loc,
e
);
None
}
}
})
.collect()
}
/// Create area lights for shapes that reference one. Produces a map from
/// shape index to a vec of lights.
/// Must be called after shapes are loaded but before upload_shapes.
pub fn create_area_lights(
&self,
loaded_shapes: &[Vec<Shape>],
shape_entities: &[ShapeSceneEntity],
textures: &NamedTextures,
arena: &Arena,
) -> HashMap<usize, Vec<Ptr<Light>>> {
let light_state = self.light_state.lock();
let mut shape_lights: HashMap<usize, Vec<Ptr<Light>>> = HashMap::new();
for (i, entity) in shape_entities.iter().enumerate() {
let light_idx = match entity.light_index {
Some(idx) => idx,
None => continue,
};
let shapes = match loaded_shapes.get(i) {
Some(s) if !s.is_empty() => s,
_ => continue,
};
let al_entity = &light_state.area_lights[light_idx];
let alpha_tex = Self::get_alpha_texture(
&entity.base.parameters,
&entity.base.loc,
&textures.float_textures,
);
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
// Use the film colorspace as fallback for area light emission
let film_cs = self.film_colorspace.lock();
let colorspace_ref = al_entity
.parameters
.color_space
.as_ref()
.or(film_cs.as_ref());
let render_from_light = *entity.render_from_object;
let lights: Vec<Ptr<Light>> = shapes
.iter()
.filter_map(|shape| {
match crate::core::light::create_area_light(
render_from_light,
None,
&al_entity.parameters,
&al_entity.loc,
shape,
alpha_ref,
colorspace_ref.map(|cs| cs.as_ref()),
arena,
) {
Ok(light) => Some(light),
Err(e) => {
log::error!("{}: failed to create area light: {}", al_entity.loc, e);
None
}
}
})
.collect();
if !lights.is_empty() {
shape_lights.insert(i, lights);
}
}
shape_lights
}
pub fn create_aggregate(
&self,
textures: &NamedTextures,
named_materials: &HashMap<String, Material>,
materials: &[Material],
arena: &mut Arena,
) -> (Vec<Primitive>, Vec<Arc<Light>>) {
let entities = self.shapes.lock();
let animated = self.animated_shapes.lock();
let light_state = self.light_state.lock();
let media = self.media_state.lock();
let film_cs = self.film_colorspace.lock();
let mut primitives = Vec::new();
let mut area_lights = Vec::new();
for entity in entities.iter() {
Self::build_primitives_for_entity(
entity,
textures,
named_materials,
materials,
&light_state,
&media,
film_cs.as_ref().map(|v| &**v),
arena,
&mut primitives,
&mut area_lights,
);
}
for entity in animated.iter() {
Self::build_animated_primitives_for_entity(
entity,
textures,
named_materials,
materials,
&light_state,
&media,
film_cs.as_ref().map(|v| &**v),
arena,
&mut primitives,
&mut area_lights,
);
}
(primitives, area_lights)
}
fn build_primitives_inner(
shapes: Vec<Ptr<Shape>>,
mtl: Material,
alpha_tex: &Option<Arc<FloatTexture>>,
mi: MediumInterface,
al_params: Option<&SceneEntity>,
render_from_light: Transform,
film_cs: Option<&RGBColorSpace>,
arena: &mut Arena,
area_lights: &mut Vec<Arc<Light>>,
) -> Vec<(Ptr<Shape>, Ptr<Light>, Ptr<GPUFloatTexture>)> {
shapes
.into_iter()
.map(|shape| {
let area_light_ptr = al_params
.and_then(|al_entity| {
let cs = al_entity.parameters.color_space.as_deref().or(film_cs);
let default_alpha = Arc::new(FloatTexture::default());
let alpha_ref = alpha_tex.as_ref().unwrap_or(&default_alpha);
crate::core::light::create_area_light(
render_from_light,
None,
&al_entity.parameters,
&al_entity.loc,
&shape,
alpha_ref,
cs,
arena,
)
.ok()
})
.unwrap_or(Ptr::null());
if !area_light_ptr.is_null() {
area_lights.push(Arc::new(unsafe { &*area_light_ptr.as_raw() }.clone()));
}
let alpha_ptr = alpha_tex
.as_ref()
.map(|t| arena.upload(t.as_ref()))
.unwrap_or(Ptr::null());
(shape, area_light_ptr, alpha_ptr)
})
.collect()
}
fn build_primitives_for_entity(
entity: &ShapeSceneEntity,
textures: &NamedTextures,
named_materials: &HashMap<String, Material>,
materials: &[Material],
light_state: &LightState,
media: &MediaState,
film_cs: Option<&RGBColorSpace>,
arena: &mut Arena,
primitives: &mut Vec<Primitive>,
area_lights: &mut Vec<Arc<Light>>,
) {
let shapes = Shape::create(
&entity.base.name,
*entity.render_from_object.as_ref(),
*entity.object_from_render.as_ref(),
entity.reverse_orientation,
entity.base.parameters.clone(),
&textures.float_textures,
entity.base.loc.clone(),
arena,
)
.unwrap_or_else(|e| {
eprintln!("Shape '{}' failed: {}", entity.base.name, e);
Vec::new()
});
let mtl = match &entity.material {
MaterialRef::Name(name) => named_materials.get(name).copied(),
MaterialRef::Index(idx) => materials.get(*idx).copied(),
MaterialRef::None => None,
}
.unwrap_or_else(|| crate::core::material::default_diffuse_material(arena));
let alpha_tex = Self::get_alpha_texture(
&entity.base.parameters,
&entity.base.loc,
&textures.float_textures,
);
let mi = MediumInterface {
inside: media
.map
.get(&entity.inside_medium)
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
outside: media
.map
.get(&entity.outside_medium)
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
};
let al_params = entity.light_index.map(|idx| &light_state.area_lights[idx]);
let built = Self::build_primitives_inner(
shapes,
mtl,
&alpha_tex,
mi.clone(),
al_params,
*entity.render_from_object,
film_cs,
arena,
area_lights,
);
for (shape, light_ptr, alpha_ptr) in built {
let prim = if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape, Ptr::from(&mtl)))
} else {
Primitive::Geometric(GeometricPrimitive::new(
shape,
arena.alloc(mtl),
light_ptr,
mi.clone(),
alpha_ptr,
))
};
primitives.push(prim);
}
}
fn build_animated_primitives_for_entity(
entity: &AnimatedShapeSceneEntity,
textures: &NamedTextures,
named_materials: &HashMap<String, Material>,
materials: &[Material],
light_state: &LightState,
media: &MediaState,
film_cs: Option<&RGBColorSpace>,
arena: &mut Arena,
primitives: &mut Vec<Primitive>,
area_lights: &mut Vec<Arc<Light>>,
) {
let shapes = Shape::create(
&entity.transformed_base.base.name,
entity.transformed_base.render_from_object.start_transform,
entity
.transformed_base
.render_from_object
.start_transform
.inverse(),
entity.reverse_orientation,
entity.transformed_base.base.parameters.clone(),
&textures.float_textures,
entity.transformed_base.base.loc.clone(),
arena,
)
.unwrap_or_else(|e| {
eprintln!(
"Animated shape '{}' failed: {}",
entity.transformed_base.base.name, e
);
Vec::new()
});
let mtl = match &entity.material {
MaterialRef::Name(name) => named_materials.get(name).copied(),
MaterialRef::Index(idx) => materials.get(*idx).copied(),
MaterialRef::None => None,
}
.unwrap_or_else(|| crate::core::material::default_diffuse_material(arena));
let alpha_tex = Self::get_alpha_texture(
&entity.transformed_base.base.parameters,
&entity.transformed_base.base.loc,
&textures.float_textures,
);
let mi = MediumInterface {
inside: media
.map
.get(&entity.inside_medium)
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
outside: media
.map
.get(&entity.outside_medium)
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
};
let al_params = entity.light_index.map(|idx| &light_state.area_lights[idx]);
let built = Self::build_primitives_inner(
shapes,
mtl,
&alpha_tex,
mi.clone(),
al_params,
entity.transformed_base.render_from_object.start_transform,
film_cs,
arena,
area_lights,
);
for (shape, light_ptr, alpha_ptr) in built {
let base_prim =
if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape, Ptr::from(&mtl)))
} else {
Primitive::Geometric(GeometricPrimitive::new(
shape,
arena.alloc(mtl),
light_ptr,
mi.clone(),
alpha_ptr,
))
};
primitives.push(Primitive::Animated(AnimatedPrimitive {
primitive: arena.alloc(base_prim),
render_from_primitive: arena.alloc(entity.transformed_base.render_from_object),
}));
}
}
fn upload_shapes(
&self,
arena: &Arena,
entities: &[ShapeSceneEntity],
loaded: Vec<ShapeWithContext>,
lookup: &SceneLookup,
) -> Vec<Primitive> {
let mut primitives = Vec::new();
let mut count = 0;
for shape_ctx in loaded {
count += 1;
if count % 500_000 == 0 {
eprintln!(" processed {} primitives", count);
}
let entity = &entities[shape_ctx.entity_index];
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_or_else(|| crate::core::material::default_diffuse_material(arena));
let mi = MediumInterface {
inside: lookup
.find_medium(&entity.inside_medium, &entity.base.loc)
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
outside: lookup
.find_medium(&entity.outside_medium, &entity.base.loc)
.map(|m| Ptr::from(m.as_ref()))
.unwrap_or(Ptr::null()),
};
let shape_lights_opt = lookup
.shape_lights
.get(&shape_ctx.entity_index)
.and_then(|lights| lights.get(shape_ctx.shape_index));
let light_ptr = shape_lights_opt.copied().unwrap_or(Ptr::null());
let shape_ptr = shape_ctx.shape;
let prim = if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() {
Primitive::Simple(SimplePrimitive::new(shape_ptr, Ptr::from(&mtl)))
} else {
Primitive::Geometric(GeometricPrimitive::new(
shape_ptr,
arena.alloc(mtl),
light_ptr,
mi.clone(),
arena.upload(alpha_tex),
))
};
primitives.push(prim);
}
primitives
}
fn upload_animated_shapes(
&self,
_arena: &mut Arena,
_entities: &[AnimatedShapeSceneEntity],
_loaded: Vec<Ptr<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(&params.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 = HostImage::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<HostImage>>> {
let filename = resolve_filename(&params.get_one_string("normalmap", "")?);
if filename.is_empty() {
return Ok(None);
}
Ok(state.normal_maps.get(&filename).cloned())
}
fn get_alpha_texture(
params: &ParameterDictionary,
loc: &FileLoc,
textures: &HashMap<String, Arc<FloatTexture>>,
) -> Option<Arc<FloatTexture>> {
let name = params.get_texture("alpha");
if name.is_empty() {
return None;
}
match textures.get(&name) {
Some(tex) => Some(tex.clone()),
None => panic!("{:?}: Alpha texture '{}' not found", loc, name),
}
}
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
}
}