diff --git a/shared/src/bxdfs/diffuse.rs b/shared/src/bxdfs/diffuse.rs index f5c8d63..01b4429 100644 --- a/shared/src/bxdfs/diffuse.rs +++ b/shared/src/bxdfs/diffuse.rs @@ -7,7 +7,7 @@ use crate::{Float, INV_PI}; use core::any::Any; #[repr(C)] -#[derive(Debug, Copy, Clone)] +#[derive(Debug, Copy, Clone, Default)] pub struct DiffuseBxDF { pub r: SampledSpectrum, } diff --git a/shared/src/cameras/perspective.rs b/shared/src/cameras/perspective.rs index 72e9b84..e6ced0c 100644 --- a/shared/src/cameras/perspective.rs +++ b/shared/src/cameras/perspective.rs @@ -115,10 +115,11 @@ impl CameraTrait for PerspectiveCamera { r.d = (p_focus - r.o).normalize(); } - let ray = self.render_from_camera(&r, &mut None); + let mut ray = self.render_from_camera(&r, &mut None); + ray.d = ray.d.normalize(); Some(CameraRay { ray, - weight: SampledSpectrum::default(), + weight: SampledSpectrum::new(1.), }) } } diff --git a/shared/src/core/bsdf.rs b/shared/src/core/bsdf.rs index 5b1415f..9d95d13 100644 --- a/shared/src/core/bsdf.rs +++ b/shared/src/core/bsdf.rs @@ -1,18 +1,18 @@ -use crate::Float; use crate::core::bxdf::{BxDF, BxDFFlags, BxDFTrait, FArgs, TransportMode}; use crate::core::geometry::{Frame, Normal3f, Point2f, Vector3f, VectorLike}; use crate::spectra::SampledSpectrum; use crate::utils::Ptr; +use crate::Float; #[repr(C)] #[derive(Copy, Clone, Debug, Default)] pub struct BSDF { - bxdf: Ptr, + bxdf: BxDF, shading_frame: Frame, } impl BSDF { - pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: Ptr) -> Self { + pub fn new(ns: Normal3f, dpdus: Vector3f, bxdf: BxDF) -> Self { Self { bxdf, shading_frame: Frame::new(dpdus.normalize(), Vector3f::from(ns)), @@ -20,11 +20,11 @@ impl BSDF { } pub fn is_valid(&self) -> bool { - !self.bxdf.is_null() + !self.bxdf.flags().is_empty() } pub fn flags(&self) -> BxDFFlags { - if self.bxdf.is_null() { + if !self.is_valid() { // Either this, or transmissive for seethrough return BxDFFlags::empty(); } @@ -45,7 +45,7 @@ impl BSDF { wi_render: Vector3f, mode: TransportMode, ) -> Option { - if self.bxdf.is_null() { + if !self.is_valid() { return None; } @@ -66,7 +66,7 @@ impl BSDF { u2: Point2f, f_args: FArgs, ) -> Option { - let bxdf = unsafe { self.bxdf.as_ref() }; + let bxdf = self.bxdf; let sampling_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits()); let wo = self.render_to_local(wo_render); @@ -85,7 +85,7 @@ impl BSDF { } pub fn pdf(&self, wo_render: Vector3f, wi_render: Vector3f, f_args: FArgs) -> Float { - if self.bxdf.is_null() { + if !self.is_valid() { return 0.0; } let sample_flags = BxDFFlags::from_bits_truncate(f_args.sample_flags.bits()); @@ -101,7 +101,7 @@ impl BSDF { } pub fn rho_u(&self, u1: &[Point2f], uc: &[Float], u2: &[Point2f]) -> SampledSpectrum { - if self.bxdf.is_null() { + if !self.is_valid() { return SampledSpectrum::default(); } @@ -109,7 +109,7 @@ impl BSDF { } pub fn rho_wo(&self, wo_render: Vector3f, uc: &[Float], u: &[Point2f]) -> SampledSpectrum { - if self.bxdf.is_null() { + if !self.is_valid() { return SampledSpectrum::default(); } @@ -118,8 +118,8 @@ impl BSDF { } pub fn regularize(&mut self) { - if !self.bxdf.is_null() { - let mut bxdf = unsafe { *self.bxdf.as_raw() }; + if !self.is_valid() { + let bxdf = &mut self.bxdf; bxdf.regularize(); } } diff --git a/shared/src/core/bxdf.rs b/shared/src/core/bxdf.rs index d9434d5..b73df2a 100644 --- a/shared/src/core/bxdf.rs +++ b/shared/src/core/bxdf.rs @@ -155,3 +155,9 @@ pub enum BxDF { CoatedConductor(CoatedConductorBxDF), NormalizedFresnel(NormalizedFresnelBxDF), } + +impl Default for BxDF { + fn default() -> Self { + BxDF::Diffuse(DiffuseBxDF::default()) + } +} diff --git a/shared/src/core/camera.rs b/shared/src/core/camera.rs index bfbe04d..12dd9f9 100644 --- a/shared/src/core/camera.rs +++ b/shared/src/core/camera.rs @@ -41,6 +41,10 @@ pub struct CameraTransform { } impl CameraTransform { + pub fn render_from_world(&self) -> Transform { + self.world_from_render.inverse() + } + pub fn from_world( world_from_camera: AnimatedTransform, rendering_space: RenderingCoordinateSystem, diff --git a/shared/src/core/film.rs b/shared/src/core/film.rs index 4dfbee0..c4fa1f8 100644 --- a/shared/src/core/film.rs +++ b/shared/src/core/film.rs @@ -115,6 +115,9 @@ impl RGBFilm { _vi: Option<&VisibleSurface>, weight: Float, ) { + if !self.base.pixel_bounds.contains_exclusive(p_film) { + return; + } let sensor = self.get_sensor(); let mut rgb = sensor.to_sensor_rgb(l, lambda); let m = rgb.into_iter().copied().fold(f32::NEG_INFINITY, f32::max); diff --git a/shared/src/core/filter.rs b/shared/src/core/filter.rs index 65cd6e2..10beb20 100644 --- a/shared/src/core/filter.rs +++ b/shared/src/core/filter.rs @@ -75,17 +75,17 @@ pub enum Filter { impl FilterTrait for Ptr { fn radius(&self) -> Vector2f { - unsafe { self.as_ref().radius() } + self.radius() } fn integral(&self) -> Float { - unsafe { self.as_ref().integral() } + self.as_ref().integral() } fn evaluate(&self, p: Point2f) -> Float { - unsafe { self.as_ref().evaluate(p) } + self.evaluate(p) } fn sample(&self, p: Point2f) -> FilterSample { - unsafe { self.as_ref().sample(p) } + self.sample(p) } } diff --git a/shared/src/core/geometry/bounds.rs b/shared/src/core/geometry/bounds.rs index 70808fd..8e37d76 100644 --- a/shared/src/core/geometry/bounds.rs +++ b/shared/src/core/geometry/bounds.rs @@ -1,5 +1,5 @@ use super::{Float, NumFloat}; -use super::{Point, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f}; +use super::{Point, Point2i, Point2f, Point3, Point3f, Vector, Vector2, Vector2f, Vector3, Vector3f}; use crate::core::geometry::traits::{SqrtExt, VectorLike}; use crate::core::geometry::{max, min}; use crate::utils::gpu_array_from_fn; @@ -349,3 +349,35 @@ impl Bounds3f { (t_min < ray_t_max) && (t_max > 0.0) } } + +pub struct BoundsPixelIterator { + bounds: Bounds2i, + current: Point2i, +} + +impl Iterator for BoundsPixelIterator { + type Item = Point2i; + fn next(&mut self) -> Option { + if self.current.y() >= self.bounds.p_max.y() { + return None; + } + let result = self.current; + let mut x = self.current.x() + 1; + let mut y = self.current.y(); + if x >= self.bounds.p_max.x() { + x = self.bounds.p_min.x(); + y += 1; + } + self.current = Point2i::new(x, y); + Some(result) + } +} + +impl Bounds2i { + pub fn pixels(&self) -> BoundsPixelIterator { + BoundsPixelIterator { + bounds: *self, + current: self.p_min, + } + } +} diff --git a/shared/src/core/interaction.rs b/shared/src/core/interaction.rs index 13fef6c..fdc9683 100644 --- a/shared/src/core/interaction.rs +++ b/shared/src/core/interaction.rs @@ -590,13 +590,13 @@ impl SurfaceInteraction { #[cfg(not(target_os = "cuda"))] pub fn set_intersection_properties( &mut self, - mtl: &Material, - area: &Light, - ray_medium: &Medium, + mtl: Ptr, + area: Ptr, + ray_medium: Ptr, prim_medium_interface: MediumInterface, ) { - self.material = Ptr::from(mtl); - self.area_light = Ptr::from(area); + self.material = mtl; + self.area_light = area; if prim_medium_interface.is_medium_transition() { self.common.medium_interface = prim_medium_interface; diff --git a/shared/src/core/material.rs b/shared/src/core/material.rs index dbbffe8..49c50df 100644 --- a/shared/src/core/material.rs +++ b/shared/src/core/material.rs @@ -2,7 +2,6 @@ use crate::materials::*; use core::ops::Deref; use enum_dispatch::enum_dispatch; -use crate::Float; use crate::bxdfs::{ CoatedConductorBxDF, CoatedDiffuseBxDF, ConductorBxDF, DielectricBxDF, DiffuseBxDF, }; @@ -19,9 +18,10 @@ use crate::core::texture::{ }; use crate::materials::*; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::Ptr; use crate::utils::hash::hash_float; use crate::utils::math::clamp; +use crate::utils::Ptr; +use crate::Float; #[repr(C)] #[derive(Clone, Debug, Copy)] @@ -194,7 +194,6 @@ pub enum Material { Mix(MixMaterial), } - // TODO: THIS IS A HACK JUST FOR TESTING impl PartialEq for Material { fn eq(&self, other: &Self) -> bool { diff --git a/shared/src/core/primitive.rs b/shared/src/core/primitive.rs index d61bdc9..3db89f3 100644 --- a/shared/src/core/primitive.rs +++ b/shared/src/core/primitive.rs @@ -43,7 +43,7 @@ impl PrimitiveTrait for GeometricPrimitive { fn intersect(&self, r: &Ray, t_max: Option) -> Option { let mut si = self.shape.intersect(r, t_max)?; if !self.alpha.is_null() { - let alpha = unsafe { &self.alpha.as_ref() }; + let alpha = &self.alpha.get().unwrap(); let ctx = TextureEvalContext::from(&si.intr); let a = alpha.evaluate(&ctx); if a < 1.0 { diff --git a/shared/src/core/shape.rs b/shared/src/core/shape.rs index 228529f..bb69f36 100644 --- a/shared/src/core/shape.rs +++ b/shared/src/core/shape.rs @@ -43,7 +43,7 @@ impl ShapeIntersection { ray_medium: Ptr, ) { self.intr - .set_intersection_properties(&mtl, &area, &ray_medium, prim_medium_interface); + .set_intersection_properties(mtl, area, ray_medium, prim_medium_interface); } } diff --git a/shared/src/core/spectrum.rs b/shared/src/core/spectrum.rs index 232d352..ad458c6 100644 --- a/shared/src/core/spectrum.rs +++ b/shared/src/core/spectrum.rs @@ -40,10 +40,10 @@ pub enum Spectrum { impl SpectrumTrait for Ptr { fn evaluate(&self, lambda: Float) -> Float { - unsafe { self.as_ref().evaluate(lambda) } + self.evaluate(lambda) } fn max_value(&self) -> Float { - unsafe { self.as_ref().max_value() } + self.max_value() } } diff --git a/shared/src/lights/diffuse.rs b/shared/src/lights/diffuse.rs index d7c4b7a..b093188 100644 --- a/shared/src/lights/diffuse.rs +++ b/shared/src/lights/diffuse.rs @@ -126,8 +126,7 @@ impl LightTrait for DiffuseAreaLight { rgb[c] = self.image.bilerp_channel(uv, c as i32); } - let cs_ref = unsafe { self.colorspace.as_ref() }; - let spec = RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero()); + let spec = RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()); self.scale * spec.sample(lambda) } else { @@ -150,8 +149,7 @@ impl LightTrait for DiffuseAreaLight { rgb[c] = self.image.get_channel(Point2i::new(x, y), c as i32); } - let cs_ref = unsafe { self.colorspace.as_ref() }; - l += RGBIlluminantSpectrum::new(cs_ref, rgb.clamp_zero()).sample(&lambda); + l += RGBIlluminantSpectrum::new(&self.colorspace, rgb.clamp_zero()).sample(&lambda); } } l *= self.scale / (self.image.resolution().x() * self.image.resolution().y()) as Float; diff --git a/shared/src/materials/coated.rs b/shared/src/materials/coated.rs index a3813ba..6274b80 100644 --- a/shared/src/materials/coated.rs +++ b/shared/src/materials/coated.rs @@ -118,7 +118,7 @@ impl MaterialTrait for CoatedDiffuseMaterial { self.seed, )); - BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) + BSDF::new(ctx.ns, ctx.dpdus, bxdf) } fn get_bssrdf( @@ -245,7 +245,7 @@ impl MaterialTrait for CoatedConductorMaterial { let (mut ce, mut ck) = if !self.conductor_eta.is_null() { let k_tex = self.k; let ce = tex_eval.evaluate_spectrum(&self.conductor_eta, ctx, lambda); - let ck = tex_eval.evaluate_spectrum(unsafe { k_tex.as_ref() }, ctx, lambda); + let ck = tex_eval.evaluate_spectrum(k_tex.get().unwrap(), ctx, lambda); (ce, ck) } else { let r = SampledSpectrum::clamp( @@ -288,7 +288,7 @@ impl MaterialTrait for CoatedConductorMaterial { self.n_samples, self.seed, )); - BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) + BSDF::new(ctx.ns, ctx.dpdus, bxdf) } fn get_bssrdf( diff --git a/shared/src/materials/dielectric.rs b/shared/src/materials/dielectric.rs index 5826c75..316a587 100644 --- a/shared/src/materials/dielectric.rs +++ b/shared/src/materials/dielectric.rs @@ -10,8 +10,8 @@ use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator}; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::Ptr; use crate::utils::math::clamp; +use crate::Ptr; #[repr(C)] #[derive(Clone, Copy, Debug)] @@ -51,7 +51,7 @@ impl MaterialTrait for DielectricMaterial { let distrib = TrowbridgeReitzDistribution::new(u_rough, v_rough); let bxdf = BxDF::Dielectric(DielectricBxDF::new(sampled_eta, distrib)); - BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) + BSDF::new(ctx.ns, ctx.dpdus, bxdf) } fn get_bssrdf( diff --git a/shared/src/materials/diffuse.rs b/shared/src/materials/diffuse.rs index b1100a6..94a24bd 100644 --- a/shared/src/materials/diffuse.rs +++ b/shared/src/materials/diffuse.rs @@ -11,7 +11,7 @@ use crate::core::scattering::TrowbridgeReitzDistribution; use crate::core::spectrum::{Spectrum, SpectrumTrait}; use crate::core::texture::{GPUFloatTexture, GPUSpectrumTexture, TextureEvaluator}; use crate::spectra::{SampledSpectrum, SampledWavelengths}; -use crate::utils::Ptr; +use crate::Ptr; use crate::utils::math::clamp; #[repr(C)] @@ -31,7 +31,7 @@ impl MaterialTrait for DiffuseMaterial { ) -> BSDF { let r = tex_eval.evaluate_spectrum(&self.reflectance, ctx, lambda); let bxdf = BxDF::Diffuse(DiffuseBxDF::new(r)); - BSDF::new(ctx.ns, ctx.dpdus, Ptr::from(&bxdf)) + BSDF::new(ctx.ns, ctx.dpdus, bxdf) } fn get_bssrdf( @@ -48,7 +48,7 @@ impl MaterialTrait for DiffuseMaterial { } fn get_normal_map(&self) -> Option<&Image> { - Some(&*self.normal_map) + self.normal_map.get() } fn get_displacement(&self) -> Ptr { @@ -93,7 +93,7 @@ impl MaterialTrait for DiffuseTransmissionMaterial { } fn get_normal_map(&self) -> Option<&Image> { - Some(&*self.image) + self.image.get() } fn get_displacement(&self) -> Ptr { diff --git a/shared/src/spectra/rgb.rs b/shared/src/spectra/rgb.rs index 1dca124..01525ba 100644 --- a/shared/src/spectra/rgb.rs +++ b/shared/src/spectra/rgb.rs @@ -1,8 +1,8 @@ use super::{ - DenselySampledSpectrum, LAMBDA_MAX, LAMBDA_MIN, N_SPECTRUM_SAMPLES, RGBColorSpace, - SampledSpectrum, SampledWavelengths, + DenselySampledSpectrum, RGBColorSpace, SampledSpectrum, SampledWavelengths, LAMBDA_MAX, + LAMBDA_MIN, N_SPECTRUM_SAMPLES, }; -use crate::core::color::{RGB, RGBSigmoidPolynomial, XYZ}; +use crate::core::color::{RGBSigmoidPolynomial, RGB, XYZ}; use crate::core::spectrum::SpectrumTrait; use crate::utils::Ptr; @@ -77,7 +77,7 @@ impl RGBIlluminantSpectrum { let illuminant = cs.illuminant; let m = rgb.max_component_value(); let scale = 2. * m; - let rsp = cs.to_rgb_coeffs(if scale == 1. { + let rsp = cs.to_rgb_coeffs(if scale != 0. { rgb / scale } else { RGB::new(0., 0., 0.) diff --git a/shared/src/utils/options.rs b/shared/src/utils/options.rs index ed46466..d71b90b 100644 --- a/shared/src/utils/options.rs +++ b/shared/src/utils/options.rs @@ -78,7 +78,7 @@ impl Default for PBRTOptions { image_file: "output.exr", mse_reference_image: None, mse_reference_output: None, - debug_start: Some((Point2i::default(), 0)), + debug_start: None, display_server: "", crop_window: None, pixel_bounds: None, diff --git a/shared/src/utils/ptr.rs b/shared/src/utils/ptr.rs index ef2b993..447fd97 100644 --- a/shared/src/utils/ptr.rs +++ b/shared/src/utils/ptr.rs @@ -1,3 +1,4 @@ +use core::hash::{Hash, Hasher}; use core::marker::PhantomData; use core::ops::Index; @@ -25,6 +26,12 @@ impl Eq for Ptr {} unsafe impl Send for Ptr {} unsafe impl Sync for Ptr {} +impl Hash for Ptr { + fn hash(&self, state: &mut H) { + self.ptr.hash(state); + } +} + impl Ptr { pub const fn null() -> Self { Self { @@ -44,12 +51,6 @@ impl Ptr { self.ptr } - #[inline(always)] - pub unsafe fn as_ref<'a>(self) -> &'a T { - debug_assert!(!self.is_null(), "null Ptr dereference"); - unsafe { &*self.ptr } - } - #[inline(always)] pub fn get<'a>(self) -> Option<&'a T> { if self.is_null() { @@ -59,6 +60,15 @@ impl Ptr { } } + #[inline(always)] + pub unsafe fn get_mut<'a>(self) -> Option<&'a mut T> { + if self.is_null() { + None + } else { + Some(unsafe { &mut *(self.ptr as *mut T) }) + } + } + #[inline(always)] pub unsafe fn at<'a>(self, index: usize) -> &'a T { debug_assert!(!self.is_null(), "null Ptr array access"); @@ -98,7 +108,11 @@ impl Ptr { #[inline(always)] pub fn get_slice<'a>(self, len: usize) -> Option<&'a [T]> { if self.is_null() { - if len == 0 { Some(&[]) } else { None } + if len == 0 { + Some(&[]) + } else { + None + } } else { Some(unsafe { core::slice::from_raw_parts(self.ptr, len) }) } diff --git a/shared/src/utils/transform.rs b/shared/src/utils/transform.rs index 1a0c153..46bc0e6 100644 --- a/shared/src/utils/transform.rs +++ b/shared/src/utils/transform.rs @@ -12,9 +12,9 @@ use crate::core::geometry::{ use crate::core::interaction::{ Interaction, InteractionBase, InteractionTrait, MediumInteraction, SurfaceInteraction, }; -use anyhow::{bail, Context, Result}; use crate::utils::gpu_array_from_fn; use crate::{gamma, Float}; +use anyhow::{bail, Context, Result}; #[repr(C)] #[derive(Debug, Copy, Clone)] @@ -113,36 +113,25 @@ impl TransformGeneric { } } - pub fn apply_to_vector(&self, p: Vector3f) -> Vector3f { - let x = p.x(); - let y = p.y(); - let z = p.z(); - let xp = self.m[0][0] * x + self.m[0][1] * y + self.m[0][2] * z; - let yp = self.m[1][0] * x + self.m[1][1] * y + self.m[1][2] * z; - let zp = self.m[2][0] * x + self.m[2][1] * y + self.m[2][2] * z; - let wp = self.m[3][0] * x + self.m[3][1] * y + self.m[3][2] * z; + pub fn apply_to_vector(&self, v: Vector3f) -> Vector3f { + let x = v.x(); + let y = v.y(); + let z = v.z(); + let xv = self.m[0][0] * x + self.m[0][1] * y + self.m[0][2] * z; + let yv = self.m[1][0] * x + self.m[1][1] * y + self.m[1][2] * z; + let zv = self.m[2][0] * x + self.m[2][1] * y + self.m[2][2] * z; + Vector3f::new(xv, yv, zv) - if wp == 1. { - Vector3f::new(xp, yp, zp) - } else { - Vector3f::new(xp / wp, yp / wp, zp / wp) - } } pub fn apply_to_normal(&self, p: Normal3f) -> Normal3f { let x = p.x(); let y = p.y(); let z = p.z(); - let xp = self.m[0][0] * x + self.m[0][1] * y + self.m[0][2] * z; - let yp = self.m[1][0] * x + self.m[1][1] * y + self.m[1][2] * z; - let zp = self.m[2][0] * x + self.m[2][1] * y + self.m[2][2] * z; - let wp = self.m[3][0] * x + self.m[3][1] * y + self.m[3][2] * z; - - if wp == 1. { - Normal3f::new(xp, yp, zp) - } else { - Normal3f::new(xp / wp, yp / wp, zp / wp) - } + let xn = self.m_inv[0][0] * x + self.m_inv[1][1] * y + self.m_inv[2][0] * z; + let yn = self.m_inv[0][1] * x + self.m_inv[1][1] * y + self.m_inv[2][1] * z; + let zn = self.m_inv[0][2] * x + self.m_inv[1][2] * y + self.m_inv[2][2] * z; + Normal3f::new(xn, yn, zn) } pub fn apply_to_bounds(&self, b: Bounds3f) -> Bounds3f { @@ -155,18 +144,20 @@ impl TransformGeneric { } pub fn apply_to_ray(&self, r: &Ray, t_max: &mut Option) -> Ray { - let norm_squared = r.d.norm_squared(); - let mut o = Point3fi::new_from_point(r.o); + let pfi = Point3fi::new_from_point(r.o); + let mut o = self.apply_to_interval(&pfi); + let d = self.apply_to_vector(r.d); + let norm_squared = d.norm_squared(); if norm_squared > 0. { - let dt = r.d.abs().dot(o.error()) / norm_squared; - let offset = Vector3fi::new_from_vector(r.d * dt); + let dt = d.abs().dot(o.error()) / norm_squared; + let offset = Vector3fi::new_from_vector(d * dt); o = o + offset; if let Some(t) = t_max.as_mut() { *t -= dt; } } - Ray::new(o.into(), r.d, Some(r.time), r.medium) + Ray::new(o.into(), d, Some(r.time), r.medium) } pub fn apply_to_interval(&self, pi: &Point3fi) -> Point3fi { @@ -493,7 +484,7 @@ impl TransformGeneric { pub fn rotate(sin_theta: Float, cos_theta: Float, axis: impl Into) -> Self { let vec_axis: Vector3f = axis.into(); let a = vec_axis.normalize(); - let mut m: SquareMatrix = SquareMatrix::default(); + let mut m: SquareMatrix = SquareMatrix::identity(); m[0][0] = a.x() * a.x() + (1. - a.x() * a.x()) * cos_theta; m[0][1] = a.x() * a.y() * (1. - cos_theta) - a.z() * sin_theta; m[0][2] = a.x() * a.z() * (1. - cos_theta) + a.y() * sin_theta; @@ -527,7 +518,7 @@ impl TransformGeneric { let uu = u.dot(u); let vv = v.dot(v); let uv = u.dot(v); - let mut r: SquareMatrix = SquareMatrix::default(); + let mut r: SquareMatrix = SquareMatrix::identity(); for i in 0..3 { for j in 0..3 { let k = if i == j { 1. } else { 0. }; @@ -770,27 +761,27 @@ impl From for TransformGeneric { let wy = q.v.y() * q.w; let wz = q.v.z() * q.w; - let mut m = [[0.0; 4]; 4]; + let mut m_inv = [[0.0; 4]; 4]; - m[0][0] = 1. - 2. * (yy + zz); - m[0][1] = 2. * (xy - wz); - m[0][2] = 2. * (xz + wy); + m_inv[0][0] = 1. - 2. * (yy + zz); + m_inv[0][1] = 2. * (xy + wz); + m_inv[0][2] = 2. * (xz - wy); - m[1][0] = 2. * (xy + wz); - m[1][1] = 1. - 2. * (xx + zz); - m[1][2] = 2. * (yz - wx); + m_inv[1][0] = 2. * (xy - wz); + m_inv[1][1] = 1. - 2. * (xx + zz); + m_inv[1][2] = 2. * (yz + wx); - m[2][0] = 2. * (xz - wy); - m[2][1] = 2. * (yz + wx); - m[2][2] = 1. - 2. * (xx + yy); + m_inv[2][0] = 2. * (xz + wy); + m_inv[2][1] = 2. * (yz - wx); + m_inv[2][2] = 1. - 2. * (xx + yy); - m[3][3] = 1.; + m_inv[3][3] = 1.; - let m_sq = SquareMatrix::new(m); + let m_inv_mat = SquareMatrix::new(m_inv); // For a pure rotation, the inverse is the transpose. - let m_inv_sq = m_sq.transpose(); + let m = m_inv_mat.transpose(); - TransformGeneric::new(m_sq, m_inv_sq) + TransformGeneric::new(m, m_inv_mat) } } @@ -872,7 +863,22 @@ impl AnimatedTransform { let actually_animated = start_transform != end_transform; if !actually_animated { - return Self::default(); + return Self { + start_transform: *start_transform, + end_transform: *end_transform, + start_time, + end_time, + actually_animated: false, + t: gpu_array_from_fn(|_| Vector3f::default()), + r: gpu_array_from_fn(|_| Quaternion::default()), + s: gpu_array_from_fn(|_| SquareMatrix::default()), + has_rotation: false, + c1: gpu_array_from_fn(|_| DerivativeTerm::default()), + c2: gpu_array_from_fn(|_| DerivativeTerm::default()), + c3: gpu_array_from_fn(|_| DerivativeTerm::default()), + c4: gpu_array_from_fn(|_| DerivativeTerm::default()), + c5: gpu_array_from_fn(|_| DerivativeTerm::default()), + }; } let (t0, r_temp, s0) = start_transform.decompose(); @@ -2120,7 +2126,7 @@ pub fn look_at( look: impl Into, up: impl Into, ) -> Result> { - let mut world_from_camera: SquareMatrix = SquareMatrix::default(); + let mut world_from_camera: SquareMatrix = SquareMatrix::identity(); // Initialize fourth column of viewing matrix let pos: Point3f = pos.into(); let look: Point3f = look.into(); @@ -2158,6 +2164,8 @@ pub fn look_at( world_from_camera[2][2] = dir.z(); world_from_camera[3][2] = 0.; - let camera_from_world = world_from_camera.inverse().context("Failed to inverse viewing matrix")?; + let camera_from_world = world_from_camera + .inverse() + .context("Failed to inverse viewing matrix")?; Ok(TransformGeneric::new(camera_from_world, world_from_camera)) } diff --git a/src/core/film.rs b/src/core/film.rs index 205f398..a81eb13 100644 --- a/src/core/film.rs +++ b/src/core/film.rs @@ -218,7 +218,7 @@ impl CreatePixelSensor for PixelSensor { let target_white = output_colorspace.w; xyz_from_sensor_rgb = white_balance(source_white, target_white); } else { - xyz_from_sensor_rgb = SquareMatrix::::default(); + xyz_from_sensor_rgb = SquareMatrix::::identity(); } PixelSensor { diff --git a/src/core/filter.rs b/src/core/filter.rs index a0f3f93..beefd02 100644 --- a/src/core/filter.rs +++ b/src/core/filter.rs @@ -35,7 +35,9 @@ impl FilterFactory for Filter { let yw = params.get_one_float("yradius", 1.5)?; let sigma = params.get_one_float("sigma", 0.5)?; let filter = GaussianFilter::new(Vector2f::new(xw, yw), sigma); - Ok(Filter::Gaussian(arena.alloc(filter))) + let ptr = arena.alloc(filter); + log::warn!("GaussianFilter allocated at {:p}", ptr.as_raw()); + Ok(Filter::Gaussian(ptr)) } "mitchell" => { let xw = params.get_one_float("xradius", 2.)?; diff --git a/src/core/interaction.rs b/src/core/interaction.rs index e94e255..2108319 100644 --- a/src/core/interaction.rs +++ b/src/core/interaction.rs @@ -1,5 +1,4 @@ use crate::globals::get_options; -use shared::Ptr; use shared::bxdfs::DiffuseBxDF; use shared::core::bsdf::BSDF; use shared::core::bssrdf::BSSRDF; @@ -11,6 +10,7 @@ use shared::core::material::{Material, MaterialEvalContext, MaterialTrait}; use shared::core::sampler::{Sampler, SamplerTrait}; use shared::core::texture::UniversalTextureEvaluator; use shared::spectra::SampledWavelengths; +use shared::Ptr; pub trait InteractionGetter { fn get_bsdf( @@ -61,7 +61,7 @@ impl InteractionGetter for SurfaceInteraction { if get_options().force_diffuse { let rho = bsdf.rho_wo(self.common.wo, &[sampler.get1d()], &[sampler.get2d()]); let diff_bxdf = BxDF::Diffuse(DiffuseBxDF::new(rho)); - bsdf = BSDF::new(self.shading.n, self.shading.dpdu, Ptr::from(&diff_bxdf)); + bsdf = BSDF::new(self.shading.n, self.shading.dpdu, diff_bxdf); } Some(bsdf) } diff --git a/src/core/render.rs b/src/core/render.rs index 05f9f19..5e3abc3 100644 --- a/src/core/render.rs +++ b/src/core/render.rs @@ -13,29 +13,36 @@ use shared::spectra::{SampledWavelengths, LAMBDA_MAX, LAMBDA_MIN}; use shared::Float; use std::sync::Arc; -fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { +pub fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { let media = scene.create_media(); let textures = scene.create_textures(arena); - let (lights, _) = scene.create_lights(&textures, arena); let (named_materials, materials) = scene.create_materials(&textures, arena)?; + let lights = scene.create_lights(&textures, arena); + + let have_scattering = { + let shapes = scene.shapes.lock(); + let animated = scene.animated_shapes.lock(); + shapes.iter().any(|sh| !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty()) + || animated.iter().any(|sh| !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty()) + }; + let (aggregate, area_lights) = - scene.create_aggregate(&textures, &named_materials, &materials, arena); + scene.create_aggregate(&textures, &named_materials, &materials, &media, arena); + + let mut all_lights = lights; + all_lights.extend(area_lights); + let camera = scene.get_camera().unwrap(); let film = camera.get_film(); warn!("Creating integrator"); let sampler = scene.get_sampler()?; - let integrator = scene.create_integrator(camera.clone(), sampler.clone(), aggregate.clone(), lights, arena); - let mut have_scattering = false; - for sh in scene.shapes.lock().iter() { - if !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty() { - have_scattering = true; - } - } - for sh in scene.animated_shapes.lock().iter() { - if !sh.inside_medium.is_empty() || !sh.outside_medium.is_empty() { - have_scattering = true; - } - } + let integrator = scene.create_integrator( + camera.clone(), + sampler.clone(), + aggregate.clone(), + all_lights, + arena, + ); if get_options().pixel_material.is_some() { let lambda = @@ -75,20 +82,13 @@ fn render_scene(scene: &BasicScene, arena: &Arena) -> Result<()> { ); log::debug!("Distance from camera: {}\n", intr.p().distance(cr.ray.o)); - let mut is_named = false; for (name, mtl) in &named_materials { if *mtl == unsafe { *intr.material.as_ref() } { log::debug!("Named material: {}\n\n", name); - is_named = true; break; } } - // if !is_named { - // log::warn!("{}\n\n", intr.material.as_ref().to_str()); - // } - // - depth += 1; ray = intr.spawn_ray(ray.d); } diff --git a/src/core/scene/builder.rs b/src/core/scene/builder.rs index 6871b5a..8e9fc92 100644 --- a/src/core/scene/builder.rs +++ b/src/core/scene/builder.rs @@ -61,7 +61,7 @@ struct PendingAreaLight { loc: FileLoc, } -#[derive(Default, Debug, Clone)] +#[derive(Debug, Clone)] struct GraphicsState { pub current_inside_medium: String, pub current_outside_medium: String, @@ -81,6 +81,30 @@ struct GraphicsState { pub transform_end_time: Float, } +impl Default for GraphicsState { + fn default() -> Self { + Self { + color_space: Some(crate::spectra::default_colorspace_arc()), + active_transform_bits: BasicSceneBuilder::ALL_TRANSFORM_BITS, + current_inside_medium: String::new(), + current_outside_medium: String::new(), + current_material_name: String::new(), + current_material_index: None, + pending_area_light: None, + shape_attributes: Vec::new(), + light_attributes: Vec::new(), + material_attributes: Vec::new(), + medium_attributes: Vec::new(), + texture_attributes: Vec::new(), + reverse_orientation: false, + ctm: TransformSet::default(), + transform_start_time: 0.0, + transform_end_time: 1.0, + } + } +} + + #[derive(PartialEq, Eq)] enum BlockState { OptionsBlock, @@ -115,6 +139,19 @@ impl BasicSceneBuilder { pub const END_TRANSFORM_BITS: u32 = 1 << 1; pub const ALL_TRANSFORM_BITS: u32 = (1 << MAX_TRANSFORMS) - 1; + fn render_from_object_at(&self, index: usize) -> Transform { + self.render_from_world * self.graphics_state.ctm[index] + } + + fn render_from_object(&self) -> AnimatedTransform { + AnimatedTransform::new( + &self.render_from_object_at(0), + self.graphics_state.transform_start_time, + &self.render_from_object_at(1), + self.graphics_state.transform_end_time, + ) + } + pub fn new(scene: Arc) -> Self { Self { scene, @@ -343,7 +380,6 @@ impl ParserTarget for BasicSceneBuilder { 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 @@ -359,7 +395,8 @@ impl ParserTarget for BasicSceneBuilder { 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]; + + self.render_from_world = camera_transform.render_from_world(); let parameters = self.make_params(params, &loc)?; @@ -526,7 +563,7 @@ impl ParserTarget for BasicSceneBuilder { Ok(()) } - fn world_begin(&mut self, loc: FileLoc, arena: Arc) -> Result<(), ParserError> { + fn world_begin(&mut self, loc: FileLoc, arena: &Arena) -> Result<(), ParserError> { self.verify_options("WorldBegin", &loc)?; self.current_block = BlockState::WorldBlock; for i in 0..MAX_TRANSFORMS { @@ -684,7 +721,7 @@ impl ParserTarget for BasicSceneBuilder { }; let entity = TextureSceneEntity { base, - render_from_object: AnimatedTransform::from_transform(&self.graphics_state.ctm[0]), + render_from_object: self.render_from_object(), }; if type_name == "float" { @@ -739,12 +776,7 @@ impl ParserTarget for BasicSceneBuilder { self.graphics_state.color_space.clone(), )?; - let render_from_light = AnimatedTransform::new( - &self.graphics_state.ctm.t[0], - self.graphics_state.transform_start_time, - &self.graphics_state.ctm.t[1], - self.graphics_state.transform_end_time, - ); + let render_from_light = self.render_from_object(); let entity = LightSceneEntity { transformed_base: TransformedSceneEntity { @@ -791,7 +823,7 @@ impl ParserTarget for BasicSceneBuilder { self.graphics_state.color_space.clone(), )?; - let render_from_object = self.graphics_state.ctm[0]; + let render_from_object = self.render_from_object_at(0); let object_from_render = render_from_object.inverse(); let light_index = if let Some(ref al) = self.graphics_state.pending_area_light { @@ -841,6 +873,7 @@ impl ParserTarget for BasicSceneBuilder { Ok(()) } + fn object_begin(&mut self, _name: &str, _loc: FileLoc) -> Result<(), ParserError> { Ok(()) } diff --git a/src/core/scene/mod.rs b/src/core/scene/mod.rs index 7b89973..fe75df5 100644 --- a/src/core/scene/mod.rs +++ b/src/core/scene/mod.rs @@ -5,7 +5,5 @@ pub mod state; pub use builder::BasicSceneBuilder; pub use entities::*; -pub use scene::{BasicScene, SceneLookup}; +pub use scene::BasicScene; pub use state::*; - - diff --git a/src/core/scene/scene.rs b/src/core/scene/scene.rs index 3f25ecb..d369301 100644 --- a/src/core/scene/scene.rs +++ b/src/core/scene/scene.rs @@ -18,6 +18,7 @@ use crate::{Arena, ArenaUpload, FileLoc}; use anyhow::{anyhow, Result}; use parking_lot::Mutex; use shared::core::aggregates::{BVHAggregate, SplitMethod}; +use shared::core::camera::CameraTrait; use shared::core::camera::{Camera, CameraTransform}; use shared::core::color::LINEAR; use shared::core::film::Film; @@ -36,47 +37,94 @@ use shared::{Ptr, Transform}; use std::collections::HashMap; use std::sync::Arc; -pub struct SceneLookup<'a> { - pub textures: &'a NamedTextures, - pub media: &'a HashMap>, - pub named_materials: &'a HashMap, - pub materials: &'a Vec, - pub shape_lights: &'a HashMap>, -} - -impl<'a> SceneLookup<'a> { - pub fn find_medium(&self, name: &str, loc: &FileLoc) -> Option> { - if name.is_empty() { - return None; - } - self.media.get(name).cloned().or_else(|| { +fn find_medium( + media: &HashMap>, + name: &str, + loc: &FileLoc, +) -> Option> { + if name.is_empty() { + return None; + } + match media.get(name) { + Some(m) => Some(Arc::clone(m)), + None => { log::error!("{}: medium '{}' not defined", loc, name); None - }) - } - - pub fn resolve_material(&self, mat_ref: &MaterialRef, loc: &FileLoc) -> Option { - 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, } } } +fn resolve_medium_interface( + media: &HashMap>, + inside: &str, + outside: &str, + loc: &FileLoc, +) -> MediumInterface { + MediumInterface { + inside: find_medium(media, inside, loc) + .as_ref() + .map(|m| Ptr::from(m.as_ref())) + .unwrap_or(Ptr::null()), + outside: find_medium(media, outside, loc) + .as_ref() + .map(|m| Ptr::from(m.as_ref())) + .unwrap_or(Ptr::null()), + } +} + +fn resolve_material( + mat_ref: &MaterialRef, + named_materials: &HashMap, + materials: &[Material], + loc: &FileLoc, + arena: &Arena, +) -> Material { + match mat_ref { + MaterialRef::Name(name) => match named_materials.get(name) { + Some(m) => *m, + None => { + log::error!("{}: named material '{}' not found", loc, name); + crate::core::material::default_diffuse_material(arena) + } + }, + MaterialRef::Index(idx) => { + if *idx < materials.len() { + materials[*idx] + } else { + log::error!("{}: material index {} out of bounds", loc, idx); + crate::core::material::default_diffuse_material(arena) + } + } + MaterialRef::None => crate::core::material::default_diffuse_material(arena), + } +} + +/// Resolve alpha texture from parameters +fn get_alpha_texture( + params: &ParameterDictionary, + loc: &FileLoc, + textures: &HashMap>, +) -> Option> { + let name = params.get_texture("alpha"); + if !name.is_empty() { + match textures.get(&name) { + Some(tex) => return Some(tex.clone()), + None => panic!( + "{}: couldn't find float texture '{}' for \"alpha\" parameter.", + loc, name + ), + } + } + let alpha = params.get_one_float("alpha", 1.0).unwrap(); + if alpha < 1.0 { + Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( + alpha, + )))) + } else { + None + } +} + pub struct BasicScene { pub integrator: Mutex>, pub accelerator: Mutex>, @@ -203,65 +251,6 @@ impl BasicScene { state.materials.len() - 1 } - fn add_texture_generic( - &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>>, - 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, @@ -285,7 +274,7 @@ impl BasicScene { tex.base.loc, &arena, ) - .expect("Could not create Float texture") + .expect("Could not create float texture") }, ) } @@ -352,22 +341,20 @@ impl BasicScene { self.instances.lock().extend(uses); } + // Texture creation pub fn create_textures(&self, arena: &Arena) -> NamedTextures { let mut state = self.texture_state.lock(); let mut float_textures: HashMap> = HashMap::new(); let mut spectrum_textures: HashMap> = 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()), @@ -375,6 +362,7 @@ impl BasicScene { unbounded_spectrum_textures: Arc::new(spectrum_textures.clone()), }; + // Serial float textures may reference already-loaded textures for (name, entity) in state.serial_float_textures.drain(..) { let render_from_texture = entity.render_from_object.start_transform; let tex_dict = @@ -405,11 +393,10 @@ impl BasicScene { .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, @@ -417,6 +404,7 @@ impl BasicScene { ) -> Result<(HashMap, Vec)> { let mut state = self.material_state.lock(); + // Finish async normal map loads let finished: Vec<_> = state.normal_map_jobs.drain().collect(); for (filename, job) in finished { match std::panic::catch_unwind(|| job.wait()) { @@ -429,6 +417,7 @@ impl BasicScene { } } + // Named materials let mut named_materials: HashMap = HashMap::new(); for (name, entity) in &state.named_materials { @@ -443,7 +432,7 @@ impl BasicScene { let mat_type = entity.parameters.get_one_string("type", "")?; if mat_type.is_empty() { - log::error!("{}: missing material type", entity.loc); + log::error!("{}: missing material type for '{}'", entity.loc, name); continue; } @@ -466,7 +455,7 @@ impl BasicScene { } Err(e) => { log::error!( - "{}: Failed to create material '{}': {}", + "{}: failed to create material '{}': {}", entity.loc, name, e @@ -475,6 +464,7 @@ impl BasicScene { } } + // Indexed materials let materials: Vec = state .materials .iter() @@ -485,7 +475,6 @@ impl BasicScene { entity.parameters.clone().into(), Some(textures), ); - Material::create( &entity.name, &tex_dict, @@ -499,7 +488,7 @@ impl BasicScene { match result { Ok(mat) => Some(mat), Err(e) => { - log::error!("{}: Failed to create material: {}", entity.loc, e); + log::error!("{}: failed to create material: {}", entity.loc, e); None } } @@ -509,227 +498,57 @@ impl BasicScene { Ok((named_materials, materials)) } - pub fn create_lights( - &self, - textures: &NamedTextures, - arena: &Arena, - ) -> (Vec>, HashMap>>) { - let shape_entities = self.shapes.lock(); - let light_state = self.light_state.lock(); - let material_state = self.material_state.lock(); - - let mut all_lights: Vec> = Vec::new(); - let mut shape_index_to_area_lights: HashMap>> = HashMap::new(); - - for (i, entity) in shape_entities.iter().enumerate() { - let light_idx = match entity.light_index { - Some(idx) => idx, - None => continue, - }; - - let material_name = match &entity.material { - MaterialRef::Name(name) => { - match material_state - .named_materials - .iter() - .find(|(n, _)| n == name) - { - Some((_, mtl_entity)) => { - match mtl_entity.parameters.get_one_string("type", "") { - Ok(t) if !t.is_empty() => t, - _ => { - log::error!( - "{}: named material '{}' missing type", - entity.base.loc, - name - ); - continue; - } - } - } - None => { - log::error!( - "{}: no named material '{}' defined.", - entity.base.loc, - name - ); - continue; - } - } - } - MaterialRef::Index(idx) => match material_state.materials.get(*idx) { - Some(mtl_entity) => mtl_entity.name.clone(), - None => { - log::error!("{}: material index {} out of bounds", entity.base.loc, idx); - continue; - } - }, - MaterialRef::None => String::new(), - }; - - if material_name == "interface" || material_name == "none" || material_name.is_empty() { - log::warn!( - "{}: Ignoring area light specification for shape with \"interface\" material.", - entity.base.loc - ); - continue; - } - - let shape_objects = match Shape::create( - &entity.base.name, - *entity.render_from_object, - *entity.object_from_render, - entity.reverse_orientation, - entity.base.parameters.clone(), - &textures.float_textures, - entity.base.loc.clone(), - arena, - ) { - Ok(shapes) => shapes, - Err(e) => { - log::error!("{}: failed to create shape: {}", entity.base.loc, e); - continue; - } - }; - - if shape_objects.is_empty() { - continue; - } - - 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); - - let inside = self.get_medium(&entity.inside_medium, &entity.base.loc); - let outside = self.get_medium(&entity.outside_medium, &entity.base.loc); - let medium_interface = MediumInterface { - inside: inside - .as_ref() - .map(|m| Ptr::from(m.as_ref())) - .unwrap_or(Ptr::null()), - outside: outside - .as_ref() - .map(|m| Ptr::from(m.as_ref())) - .unwrap_or(Ptr::null()), - }; - - let al_entity = &light_state.area_lights[light_idx]; - - let film_cs = self.film_colorspace.lock(); - let colorspace_ref = al_entity - .parameters - .color_space - .as_ref() - .or(film_cs.as_ref()); - - let mut shape_lights: Vec> = Vec::new(); - - for shape in &shape_objects { - let cs = colorspace_ref.map(|cs| cs.as_ref()); - match crate::core::light::create_area_light( - *entity.render_from_object, - None, - &al_entity.parameters, - &al_entity.loc, - shape, - alpha_ref, - cs, - arena, - ) { - Ok(light) => { - let light_arc = Arc::new(light); - all_lights.push(Arc::clone(&light_arc)); - shape_lights.push(light_arc); - } - Err(e) => { - log::error!("{}: failed to create area light: {}", al_entity.loc, e); - } - } - } - - if !shape_lights.is_empty() { - shape_index_to_area_lights.insert(i, shape_lights); + pub fn create_media(&self) -> HashMap> { + let mut state = self.media_state.lock(); + if !state.jobs.is_empty() { + let jobs: Vec<(String, AsyncJob)> = state.jobs.drain().collect(); + for (name, job) in jobs { + state.map.insert(name, Arc::new(job.wait())); } } - - log::debug!("Finished area lights"); - - (all_lights, shape_index_to_area_lights) + state.map.clone() } - pub fn create_area_lights( - &self, - loaded_shapes: &[Vec], - textures: &NamedTextures, - arena: &Arena, - ) -> HashMap> { - let shape_entities = &self.shapes.lock(); + pub fn create_lights(&self, textures: &NamedTextures, arena: &Arena) -> Vec> { let light_state = self.light_state.lock(); - let mut shape_lights: HashMap> = HashMap::new(); + let camera = self + .get_camera() + .expect("Camera must be initialized before lights"); + let camera_transform = camera.base().camera_transform.clone(); + let mut lights: Vec> = Vec::new(); - for (i, entity) in shape_entities.iter().enumerate() { - let light_idx = match entity.light_index { - Some(idx) => idx, - None => continue, - }; + // Non-area lights created from stored entities + for entity in &light_state.lights { + let medium = self.get_medium(&entity.medium, &entity.transformed_base.base.loc); - let shapes = match loaded_shapes.get(i) { - Some(s) if !s.is_empty() => s, - _ => continue, - }; + if entity.transformed_base.render_from_object.is_animated() { + log::warn!( + "{}: animated lights aren't supported, using start transform.", + entity.transformed_base.base.loc + ); + } - 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); - - 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 = 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); + match crate::core::light::create_light( + &entity.transformed_base.base.name, + entity.transformed_base.render_from_object.start_transform, + medium.map(|m| *m), + &entity.transformed_base.base.parameters, + &entity.transformed_base.base.loc, + camera_transform.clone(), + arena, + ) { + Ok(light) => lights.push(Arc::new(light)), + Err(e) => { + log::error!( + "{}: failed to create light: {}", + entity.transformed_base.base.loc, + e + ); + } } } - shape_lights + lights } pub fn create_aggregate( @@ -737,58 +556,134 @@ impl BasicScene { textures: &NamedTextures, named_materials: &HashMap, materials: &[Material], + media: &HashMap>, arena: &Arena, ) -> (Arc, Vec>) { - let entities = self.shapes.lock(); - let animated = self.animated_shapes.lock(); + let mut shapes = self.shapes.lock(); + let mut animated_shapes = self.animated_shapes.lock(); + let mut instance_defs = self.instance_definitions.lock(); + let mut instances = self.instances.lock(); let light_state = self.light_state.lock(); - let media = self.media_state.lock(); let film_cs = self.film_colorspace.lock(); + let film_cs_ref = film_cs.as_deref(); - let mut primitives = Vec::new(); - let mut area_lights = Vec::new(); + let mut all_lights: Vec> = Vec::new(); - for entity in entities.iter() { - Self::build_primitives_for_entity( - entity, + log::info!("Starting shapes"); + let mut primitives = Self::create_primitives_for_shapes( + &shapes, + textures, + named_materials, + materials, + &light_state, + media, + film_cs_ref, + arena, + &mut all_lights, + ); + + shapes.clear(); + shapes.shrink_to_fit(); + + let animated_primitives = Self::create_primitives_for_animated_shapes( + &animated_shapes, + textures, + named_materials, + materials, + &light_state, + media, + film_cs_ref, + arena, + &mut all_lights, + ); + primitives.extend(animated_primitives); + + animated_shapes.clear(); + animated_shapes.shrink_to_fit(); + log::info!("Finished shapes"); + + log::info!("Starting instances"); + let mut resolved_defs: HashMap> = HashMap::new(); + + for (name, def) in instance_defs.drain() { + let mut inst_prims = Self::create_primitives_for_shapes( + &def.shapes, textures, named_materials, materials, &light_state, - &media, - film_cs.as_ref().map(|v| &**v), + media, + film_cs_ref, arena, - &mut primitives, - &mut area_lights, + &mut all_lights, ); - } - for entity in animated.iter() { - Self::build_animated_primitives_for_entity( - entity, + let animated_inst_prims = Self::create_primitives_for_animated_shapes( + &def.animated_shapes, textures, named_materials, materials, &light_state, - &media, - film_cs.as_ref().map(|v| &**v), + media, + film_cs_ref, arena, - &mut primitives, - &mut area_lights, + &mut all_lights, ); + inst_prims.extend(animated_inst_prims); + + let aggregate = if inst_prims.len() > 1 { + let bvh = BVHAggregate::new(inst_prims, 4, SplitMethod::SAH); + Some(Primitive::BVH(arena.alloc(bvh))) + } else if inst_prims.len() == 1 { + Some(inst_prims.into_iter().next().unwrap()) + } else { + None + }; + + resolved_defs.insert(name, aggregate); } + for inst in instances.drain(..) { + let def = match resolved_defs.get(&inst.name) { + Some(Some(prim)) => prim, + Some(None) => continue, // empty instance + None => { + log::error!("{}: object instance '{}' not defined", inst.loc, inst.name); + continue; + } + }; + + let prim = match &inst.transform { + InstanceTransform::Static(xform) => { + // TransformedPrimitive wraps a primitive with a static transform + Primitive::Transformed(shared::core::primitive::TransformedPrimitive { + primitive: arena.alloc(*def), + render_from_primitive: arena.alloc(**xform), + }) + } + InstanceTransform::Animated(anim) => Primitive::Animated(AnimatedPrimitive { + primitive: arena.alloc(*def), + render_from_primitive: arena.alloc(*anim), + }), + }; + primitives.push(prim); + } + + log::info!("Finished instances"); + + log::info!("Starting top-level accelerator"); let aggregate = if !primitives.is_empty() { - BVHAggregate::new(primitives.clone(), 4, SplitMethod::SAH) + BVHAggregate::new(primitives, 4, SplitMethod::SAH) } else { BVHAggregate::empty() }; - let agg_ptr = arena.alloc(aggregate); + log::info!("Finished top-level accelerator"); - (Arc::new(Primitive::BVH(agg_ptr)), area_lights) + (Arc::new(Primitive::BVH(agg_ptr)), all_lights) } + // Integrator pub fn create_integrator( &self, camera: Arc, @@ -797,341 +692,22 @@ impl BasicScene { lights: Vec>, arena: &Arena, ) -> PathIntegrator { - let integrator = &self.integrator.lock().clone().unwrap(); - PathIntegrator::create( - integrator.parameters.clone(), - camera, - sampler, - aggregate, - lights, - PathConfig::SIMPLE, - arena, - ) - .expect("Integrator creation has failed") - } + let integrator_entity = self.integrator.lock().clone().unwrap(); + let name = &integrator_entity.name; - fn build_primitives_inner( - shapes: Vec>, - mtl: Material, - alpha_tex: &Option>, - mi: MediumInterface, - al_params: Option<&SceneEntity>, - render_from_light: Transform, - film_cs: Option<&RGBColorSpace>, - arena: &Arena, - area_lights: &mut Vec>, - ) -> Vec<(Ptr, Ptr, Ptr)> { - 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); - match crate::core::light::create_area_light( - render_from_light, - None, - &al_entity.parameters, - &al_entity.loc, - &shape, - alpha_ref, - cs, - arena, - ) { - Ok(light) => { - // Keep an Arc copy for the host-side light list - area_lights.push(Arc::new(light)); - // Alloc into arena for the GPU primitive - Some(arena.alloc(light)) - } - Err(e) => { - log::error!("Failed to create area light: {}", e); - None - } - } - }) - .unwrap_or(Ptr::null()); - - 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, - materials: &[Material], - light_state: &LightState, - media: &MediaState, - film_cs: Option<&RGBColorSpace>, - arena: &Arena, - primitives: &mut Vec, - area_lights: &mut Vec>, - ) { - 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, + match name.as_str() { + "path" | "volpath" => PathIntegrator::create( + integrator_entity.parameters.clone(), + camera, + sampler, + aggregate, + lights, + PathConfig::SIMPLE, + arena, + ) + .expect("Integrator creation failed"), + _ => panic!("Unknown integrator: {}", name), } - .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, - materials: &[Material], - light_state: &LightState, - media: &MediaState, - film_cs: Option<&RGBColorSpace>, - arena: &Arena, - primitives: &mut Vec, - area_lights: &mut Vec>, - ) { - 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, - lookup: &SceneLookup, - ) -> Vec { - 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()), - }; - - // Light is &Light from the HashMap — alloc into arena for the Ptr - let light_ptr = lookup - .shape_lights - .get(&shape_ctx.entity_index) - .and_then(|lights| lights.get(shape_ctx.shape_index)) - .map(|l| arena.alloc(*l)) - .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(), - alpha_tex - .as_ref() - .map(|t| arena.upload(t.as_ref())) - .unwrap_or(Ptr::null()), - )) - }; - - primitives.push(prim); - } - - primitives - } - - fn upload_animated_shapes( - &self, - _arena: &mut Arena, - _entities: &[AnimatedShapeSceneEntity], - _loaded: Vec>, - _lookup: &SceneLookup, - ) -> Vec { - // TODO: implement animated shape upload - Vec::new() - } - - pub fn create_media(&self) -> HashMap> { - let mut state = self.media_state.lock(); - - if !state.jobs.is_empty() { - let jobs: Vec<(String, AsyncJob)> = state.jobs.drain().collect(); - for (name, job) in jobs { - let medium = Arc::new(job.wait()); - state.map.insert(name, medium); - } - } - - state.map.clone() } // Getters @@ -1148,7 +724,366 @@ impl BasicScene { self.get_singleton(&self.film_state, "Film") } - // Helpers + pub fn get_medium(&self, name: &str, loc: &FileLoc) -> Option> { + 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 medium: Arc = Arc::new(job.wait()); + state.map.insert(name.to_string(), medium.clone()); + return Some(medium); + } + + log::error!("{}: medium '{}' is not defined.", loc, name); + None + } + + fn create_primitives_for_shapes( + shapes: &[ShapeSceneEntity], + textures: &NamedTextures, + named_materials: &HashMap, + materials: &[Material], + light_state: &LightState, + media: &HashMap>, + film_cs: Option<&RGBColorSpace>, + arena: &Arena, + area_lights: &mut Vec>, + ) -> Vec { + let mut primitives = Vec::new(); + + for entity in shapes { + let created_shapes = match Shape::create( + &entity.base.name, + *entity.render_from_object, + *entity.object_from_render, + entity.reverse_orientation, + entity.base.parameters.clone(), + &textures.float_textures, + entity.base.loc.clone(), + arena, + ) { + Ok(s) => s, + Err(e) => { + log::error!("{}: shape creation failed: {}", entity.base.loc, e); + continue; + } + }; + + if created_shapes.is_empty() { + continue; + } + + let mtl = resolve_material( + &entity.material, + named_materials, + materials, + &entity.base.loc, + arena, + ); + + let alpha_tex = get_alpha_texture( + &entity.base.parameters, + &entity.base.loc, + &textures.float_textures, + ); + + let mi = resolve_medium_interface( + media, + &entity.inside_medium, + &entity.outside_medium, + &entity.base.loc, + ); + + let al_entity = entity.light_index.map(|idx| &light_state.area_lights[idx]); + + for shape in created_shapes { + // Create area light for this shape if the entity has one + let light_ptr = al_entity + .and_then(|al| { + let cs = al.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); + match crate::core::light::create_area_light( + *entity.render_from_object, + None, + &al.parameters, + &al.loc, + &shape, + alpha_ref, + cs, + arena, + ) { + Ok(light) => { + area_lights.push(Arc::new(light)); + Some(arena.alloc(light)) + } + Err(e) => { + log::error!("{}: area light creation failed: {}", al.loc, e); + None + } + } + }) + .unwrap_or(Ptr::null()); + + // Pick SimplePrimitive when no extras are needed + let prim = + if light_ptr.is_null() && !mi.is_medium_transition() && alpha_tex.is_none() { + Primitive::Simple(SimplePrimitive::new(shape, arena.alloc(mtl))) + } else { + let alpha_ptr = alpha_tex + .as_ref() + .map(|t| arena.upload(t.as_ref())) + .unwrap_or(Ptr::null()); + + Primitive::Geometric(GeometricPrimitive::new( + shape, + arena.alloc(mtl), + light_ptr, + mi.clone(), + alpha_ptr, + )) + }; + + primitives.push(prim); + } + } + + primitives + } + + fn create_primitives_for_animated_shapes( + shapes: &[AnimatedShapeSceneEntity], + textures: &NamedTextures, + named_materials: &HashMap, + materials: &[Material], + light_state: &LightState, + media: &HashMap>, + film_cs: Option<&RGBColorSpace>, + arena: &Arena, + area_lights: &mut Vec>, + ) -> Vec { + let mut primitives = Vec::new(); + + for entity in shapes { + let start_xform = entity.transformed_base.render_from_object.start_transform; + + let created_shapes = match Shape::create( + &entity.transformed_base.base.name, + start_xform, + start_xform.inverse(), + entity.reverse_orientation, + entity.transformed_base.base.parameters.clone(), + &textures.float_textures, + entity.transformed_base.base.loc.clone(), + arena, + ) { + Ok(s) => s, + Err(e) => { + log::error!( + "{}: animated shape creation failed: {}", + entity.transformed_base.base.loc, + e + ); + continue; + } + }; + + if created_shapes.is_empty() { + continue; + } + + let mtl = resolve_material( + &entity.material, + named_materials, + materials, + &entity.transformed_base.base.loc, + arena, + ); + + let alpha_tex = get_alpha_texture( + &entity.transformed_base.base.parameters, + &entity.transformed_base.base.loc, + &textures.float_textures, + ); + + let mi = resolve_medium_interface( + media, + &entity.inside_medium, + &entity.outside_medium, + &entity.transformed_base.base.loc, + ); + + let al_entity = entity.light_index.map(|idx| &light_state.area_lights[idx]); + + if al_entity.is_some() { + log::error!( + "{}: animated area lights are not supported.", + entity.transformed_base.base.loc + ); + } + + // Build base primitives, then wrap each in AnimatedPrimitive + let mut base_prims = Vec::new(); + for shape in created_shapes { + let base = if !mi.is_medium_transition() && alpha_tex.is_none() { + Primitive::Simple(SimplePrimitive::new(shape, arena.alloc(mtl))) + } else { + let alpha_ptr = alpha_tex + .as_ref() + .map(|t| arena.upload(t.as_ref())) + .unwrap_or(Ptr::null()); + + Primitive::Geometric(GeometricPrimitive::new( + shape, + arena.alloc(mtl), + Ptr::null(), // no area light on animated shapes + mi.clone(), + alpha_ptr, + )) + }; + base_prims.push(base); + } + + // Collapse multiple sub-shapes into a BVH, then animate the whole thing + let base = if base_prims.len() > 1 { + let bvh = BVHAggregate::new(base_prims, 4, SplitMethod::SAH); + Primitive::BVH(arena.alloc(bvh)) + } else { + base_prims.into_iter().next().unwrap() + }; + + primitives.push(Primitive::Animated(AnimatedPrimitive { + primitive: arena.alloc(base), + render_from_primitive: arena.alloc(entity.transformed_base.render_from_object), + })); + } + + primitives + } + + // ======================================================================= + // Private — texture helper + // ======================================================================= + + fn add_texture_generic( + &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>>, + 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 transform", + texture.base.loc + ); + } + + // Non-image textures must be created serially (they may reference other textures) + 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(()); + } + + // Already loading this file — fall back to serial to avoid duplicate work + 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 + } + + // Material helpers + 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 = HostImage::read(path, Some(LINEAR)) + .unwrap_or_else(|e| panic!("{}: failed to read normal map: {}", filename_clone, e)); + + let rgb_desc = immeta + .image + .get_channel_desc(&["R", "G", "B"]) + .unwrap_or_else(|_| { + panic!( + "{}: 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>> { + 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_singleton( &self, @@ -1171,103 +1106,58 @@ impl BasicScene { Err(anyhow!("{} requested but not initialized!", name)) } - fn start_loading_normal_maps( + // ======================================================================= + // GPU path stubs — to be implemented with wavefront integrator + // ======================================================================= + + #[allow(dead_code)] + fn upload_shapes( &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 = 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(()) + arena: &Arena, + entities: &[ShapeSceneEntity], + loaded: Vec, + textures: &NamedTextures, + named_materials: &HashMap, + materials: &[Material], + media: &HashMap>, + shape_lights: &HashMap>, + ) -> Vec { + // TODO: GPU wavefront path — upload shapes into device-visible arena, + // build SOA primitive arrays for kernel dispatch + let _ = ( + arena, + entities, + loaded, + textures, + named_materials, + materials, + media, + shape_lights, + ); + Vec::new() } - fn get_normal_map( + #[allow(dead_code)] + fn upload_animated_shapes( &self, - state: &MaterialState, - params: &ParameterDictionary, - ) -> Result>> { - 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( - params: &ParameterDictionary, - loc: &FileLoc, - textures: &HashMap>, - ) -> Option> { - let name = params.get_texture("alpha"); - if !name.is_empty() { - match textures.get(&name) { - Some(tex) => Some(tex.clone()), - None => panic!( - "{}: couldn't find float texture '{}' for \"alpha\" parameter.", - loc, name - ), - } - } else { - let alpha = params.get_one_float("alpha", 1.0).unwrap(); - if alpha < 1.0 { - Some(Arc::new(FloatTexture::Constant(FloatConstantTexture::new( - alpha, - )))) - } else { - None - } - } - } - - pub fn get_medium(&self, name: &str, loc: &FileLoc) -> Option> { - 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 = job; - let result: Medium = job.wait(); - let medium: Arc = Arc::new(result); - state.map.insert(name.to_string(), medium.clone()); - return Some(medium); - } - - log::error!("{}: Medium \"{}\" is not defined.", loc, name); - None + arena: &Arena, + entities: &[AnimatedShapeSceneEntity], + loaded: Vec>, + textures: &NamedTextures, + named_materials: &HashMap, + materials: &[Material], + media: &HashMap>, + ) -> Vec { + // TODO: GPU wavefront path — animated shape upload + let _ = ( + arena, + entities, + loaded, + textures, + named_materials, + materials, + media, + ); + Vec::new() } } diff --git a/src/integrators/path.rs b/src/integrators/path.rs index c76a612..0e7ffde 100644 --- a/src/integrators/path.rs +++ b/src/integrators/path.rs @@ -1,10 +1,10 @@ -use super::RayIntegratorTrait; use super::base::IntegratorBase; use super::constants::*; use super::state::PathState; -use crate::Arena; +use super::RayIntegratorTrait; use crate::core::interaction::InteractionGetter; -use shared::core::bsdf::{BSDF, BSDFSample}; +use crate::Arena; +use shared::core::bsdf::{BSDFSample, BSDF}; use shared::core::bxdf::{BxDFFlags, FArgs, TransportMode}; use shared::core::camera::Camera; use shared::core::film::VisibleSurface; @@ -227,6 +227,12 @@ impl RayIntegratorTrait for PathIntegrator { let t_hit = si.t_hit(); let isect = &mut si.intr; + if state.depth == 0 { + let n = isect.n(); + let v = (n.x() + 1.0) * 0.5; + return (SampledSpectrum::from_array(&[v, v, v, v]), None); + } + // Emission from hit surface let le = isect.le(-ray.d, lambda); if !le.is_black() { @@ -308,6 +314,8 @@ impl RayIntegratorTrait for PathIntegrator { sampler: &mut Sampler, arena: &Arena, ) { + use shared::core::primitive::PrimitiveTrait; + eprintln!("BVH bounds: {:?}", &self.base.aggregate.bounds()); crate::integrators::pipeline::evaluate_pixel_sample( self, self.camera.as_ref(), diff --git a/src/integrators/pipeline.rs b/src/integrators/pipeline.rs index 96383c4..b7f9b13 100644 --- a/src/integrators/pipeline.rs +++ b/src/integrators/pipeline.rs @@ -101,7 +101,11 @@ pub fn render( return; } - let pixel_bounds = camera.get_film().pixel_bounds(); + let sample_bounds = camera.get_film().sample_bounds(); + let pixel_bounds = Bounds2i::from_points( + Point2i::new(sample_bounds.p_min.x().floor() as i32, sample_bounds.p_min.y().floor() as i32), + Point2i::new(sample_bounds.p_max.x().ceil() as i32, sample_bounds.p_max.y().ceil() as i32), +); println!( "pixel_bounds: {:?}, area: {}", pixel_bounds, @@ -165,15 +169,14 @@ pub fn render( tiles.par_iter().for_each(|tile_bounds| { let mut sampler = sampler_prototype.clone(); - for p_pixel in tile_bounds { + for p_pixel in tile_bounds.pixels() { for sample_index in wave_start..wave_end { - sampler.start_pixel_sample(*p_pixel, sample_index, None); - println!("Evaluating pixel {:?} sample {}", p_pixel, sample_index); + sampler.start_pixel_sample(p_pixel, sample_index, None); evaluate_pixel_sample( integrator, camera, &mut sampler, - *p_pixel, + p_pixel, sample_index.try_into().unwrap(), &arena, ); @@ -252,8 +255,8 @@ pub fn evaluate_pixel_sample( let filter = film.get_filter(); let camera_sample = get_camera_sample(sampler, pixel, filter); if let Some(mut camera_ray) = camera.generate_ray_differential(camera_sample, &lambda) { - debug_assert!(camera_ray.ray.d.norm() > 0.999); - debug_assert!(camera_ray.ray.d.norm() < 1.001); + // debug_assert!(camera_ray.ray.d.norm() > 0.999); + // debug_assert!(camera_ray.ray.d.norm() < 1.001); let ray_diff_scale = (sampler.samples_per_pixel() as Float).sqrt().max(0.125); if get_options().disable_pixel_jitter { camera_ray.ray.scale_differentials(ray_diff_scale); @@ -275,10 +278,10 @@ pub fn evaluate_pixel_sample( } if pixel.x() == 352 && pixel.y() == 352 && sample_index == 0 { - println!("Center pixel: L = {:?}", l); - println!(" ray origin: {:?}", camera_ray.ray.o); - println!(" ray dir: {:?}", camera_ray.ray.d); - println!(" camera_sample.p_film: {:?}", camera_sample.p_film); + eprintln!("Center pixel: L = {:?}", l); + eprintln!(" ray origin: {:?}", camera_ray.ray.o); + eprintln!(" ray dir: {:?}", camera_ray.ray.d); + eprintln!(" camera_sample.p_film: {:?}", camera_sample.p_film); } film.add_sample( diff --git a/src/lights/diffuse.rs b/src/lights/diffuse.rs index b479e2f..9b9552c 100644 --- a/src/lights/diffuse.rs +++ b/src/lights/diffuse.rs @@ -28,7 +28,7 @@ pub fn create( colorspace: Option<&RGBColorSpace>, arena: &Arena, ) -> Result { - let mut l = params.get_one_spectrum("l", None, SpectrumType::Illuminant); + let mut l = params.get_one_spectrum("L", None, SpectrumType::Illuminant); let default_cs = crate::spectra::default_colorspace(); let cs = colorspace.unwrap_or(&default_cs); let illum_spec = Spectrum::Dense(cs.illuminant); diff --git a/src/samplers/halton.rs b/src/samplers/halton.rs index 9e60d30..a1639b3 100644 --- a/src/samplers/halton.rs +++ b/src/samplers/halton.rs @@ -1,9 +1,11 @@ use super::*; use crate::globals::get_options; -use shared::utils::math::compute_radical_inverse_permutations; -use anyhow::{Result, anyhow}; +use crate::Arena; +use anyhow::{anyhow, Result}; use shared::core::geometry::Point2i; -use shared::core::sampler::{HaltonSampler, MAX_HALTON_RESOLUTION, RandomizeStrategy}; +use shared::core::sampler::{HaltonSampler, RandomizeStrategy, MAX_HALTON_RESOLUTION}; +use shared::utils::math::compute_radical_inverse_permutations; +use shared::{gbox, Ptr}; pub trait CreateHaltonSampler { fn new( @@ -11,6 +13,7 @@ pub trait CreateHaltonSampler { full_res: Point2i, randomize: RandomizeStrategy, seed: u64, + arena: &Arena, ) -> Self; } @@ -20,8 +23,11 @@ impl CreateHaltonSampler for HaltonSampler { full_res: Point2i, randomize: RandomizeStrategy, seed: u64, + arena: &Arena, ) -> Self { let digit_permutations = compute_radical_inverse_permutations(seed); + let leaked = Box::leak(gbox(digit_permutations)); + let perm_ptr = Ptr::from(leaked.as_slice()); let mut base_scales = [0u64; 2]; let mut base_exponents = [0u64; 2]; let bases = [2, 3]; @@ -46,14 +52,14 @@ impl CreateHaltonSampler for HaltonSampler { let mut mult_inverse = [0u64; 2]; mult_inverse[0] = - Self::multiplicative_inverse(base_scales[0] as i64, base_scales[0] as i64); + Self::multiplicative_inverse(base_scales[1] as i64, base_scales[0] as i64); mult_inverse[1] = - Self::multiplicative_inverse(base_scales[1] as i64, base_scales[1] as i64); + Self::multiplicative_inverse(base_scales[0] as i64, base_scales[1] as i64); Self { samples_per_pixel, randomize, - digit_permutations: digit_permutations.as_ptr().into(), + digit_permutations: perm_ptr, base_scales, base_exponents, mult_inverse, @@ -68,7 +74,7 @@ impl CreateSampler for HaltonSampler { params: &ParameterDictionary, full_res: Point2i, loc: &FileLoc, - _arena: &Arena, + arena: &Arena, ) -> Result { let options = get_options(); let nsamp = if let Some(n) = options.quick_render.then_some(1).or(options.pixel_samples) { @@ -94,8 +100,7 @@ impl CreateSampler for HaltonSampler { } }; - let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64); - // arena.alloc(sampler); + let sampler = HaltonSampler::new(nsamp, full_res, s, seed as u64, arena); Ok(Sampler::Halton(sampler)) } } diff --git a/src/utils/loopsubdiv.rs b/src/utils/loopsubdiv.rs new file mode 100644 index 0000000..5c240be --- /dev/null +++ b/src/utils/loopsubdiv.rs @@ -0,0 +1,534 @@ +use crate::utils::backend::GpuAllocator; +use shared::core::geometry::{Normal3f, Point3f, Vector3f}; +use shared::{Float, Ptr}; +use std::collections::HashMap; + +pub struct SDVertex { + pub p: Point3f, + pub start_face: Ptr, + pub child: Ptr, + pub regular: bool, + pub boundary: bool, +} + +impl SDVertex { + pub fn new(p: Point3f) -> Self { + Self { + p, + start_face: Ptr::null(), + child: Ptr::null(), + regular: false, + boundary: false, + } + } +} + +pub struct SDFace { + pub v: [Ptr; 3], + pub f: [Ptr; 3], + pub children: [Ptr; 4], +} + +impl SDFace { + pub fn new(v0: Ptr, v1: Ptr, v2: Ptr) -> Self { + Self { + v: [v0, v1, v2], + f: [Ptr::null(); 3], + children: [Ptr::null(); 4], + } + } + + pub fn vnum(&self, vert: Ptr) -> usize { + for i in 0..3 { + if self.v[i] == vert { + return i; + } + } + panic!("Basic logic error in SDFace::vnum()"); + } + + pub fn next_face(&self, vert: Ptr) -> Ptr { + self.f[self.vnum(vert)] + } + + pub fn prev_face(&self, vert: Ptr) -> Ptr { + self.f[prev(self.vnum(vert))] + } + + pub fn next_vert(&self, vert: Ptr) -> Ptr { + self.v[next(self.vnum(vert))] + } + + pub fn prev_vert(&self, vert: Ptr) -> Ptr { + self.v[prev(self.vnum(vert))] + } + + pub fn other_vert(&self, v0: Ptr, v1: Ptr) -> Ptr { + for i in 0..3 { + if self.v[i] != v0 && self.v[i] != v1 { + return self.v[i]; + } + } + panic!("Basic logic error in SDFace::other_vert()"); + } +} + +const fn next(i: usize) -> usize { + (i + 1) % 3 +} + +const fn prev(i: usize) -> usize { + (i + 2) % 3 +} + +fn canonical_key(v0: Ptr, v1: Ptr) -> (usize, usize) { + let a = v0.as_ptr() as usize; + let b = v1.as_ptr() as usize; + if a <= b { + (a, b) + } else { + (b, a) + } +} + +fn beta(valence: usize) -> Float { + if valence == 3 { + 3.0 / 16.0 + } else { + 3.0 / (8.0 * valence as Float) + } +} + +fn loop_gamma(valence: usize) -> Float { + 1.0 / (valence as Float + 3.0 / (8.0 * beta(valence))) +} + +fn valence(v: Ptr) -> usize { + let vertex = v; + let start = vertex.start_face; + if start.is_null() { + return 0; + } + + if !vertex.boundary { + let mut nf = 1; + let mut f = start; + loop { + f = f.next_face(v); + if f.is_null() { + panic!("interior vertex with broken face loop"); + } + if f == start { + break; + } + nf += 1; + } + nf + } else { + let mut nf = 1; + let mut f = start; + while !f.next_face(v).is_null() { + f = f.next_face(v); + nf += 1; + } + f = start; + while !f.prev_face(v).is_null() { + f = f.prev_face(v); + nf += 1; + } + nf + 1 + } +} + +fn one_ring(v: Ptr, out: &mut [Point3f]) { + let vertex = v; + if !vertex.boundary { + let start = vertex.start_face; + let mut face = start; + let mut i = 0; + loop { + out[i] = face.next_vert(v).p; + i += 1; + face = face.next_face(v); + if face == start { + break; + } + } + } else { + let start = vertex.start_face; + let mut face = start; + while !face.next_face(v).is_null() { + face = face.next_face(v); + } + out[0] = face.next_vert(v).p; + let mut i = 1; + loop { + out[i] = face.prev_vert(v).p; + i += 1; + let pf = face.prev_face(v); + if pf.is_null() { + break; + } + face = pf; + } + } +} + +fn weight_one_ring(v: Ptr, beta: Float) -> Point3f { + let valence = valence(v); + let mut p_ring = vec![Point3f::default(); valence]; + one_ring(v, &mut p_ring); + + let vertex = v; + let mut p = vertex.p * (1.0 - valence as Float * beta); + for i in 0..valence { + p += p_ring[i] * beta; + } + p +} + +fn weight_boundary(v: Ptr, beta: Float) -> Point3f { + let valence = valence(v); + let mut p_ring = vec![Point3f::default(); valence]; + one_ring(v, &mut p_ring); + + let vertex = v; + let mut p = vertex.p * (1.0 - 2.0 * beta); + p += p_ring[0] * beta; + p += p_ring[valence - 1] * beta; + p +} + +pub fn loop_subdivide( + arena: &Arena, + render_from_object: &Transform, + reverse_orientation: bool, + n_levels: usize, + vertex_indices: &[usize], + p: &[Point3f], +) -> TriangleMesh { + let mut vertices: Vec> = Vec::with_capacity(p.len()); + for &pos in p { + vertices.push(arena.alloc(SDVertex::new(pos))); + } + + let n_faces = vertex_indices.len() / 3; + let mut faces: Vec> = Vec::with_capacity(n_faces); + for i in 0..n_faces { + let v0 = vertices[vertex_indices[3 * i]]; + let v1 = vertices[vertex_indices[3 * i + 1]]; + let v2 = vertices[vertex_indices[3 * i + 2]]; + faces.push(arena.alloc(SDFace::new(v0, v1, v2))); + } + + for &f in &faces { + let face = f; + for j in 0..3 { + face.v[j].get_mut().unwrap().start_face = f; + } + } + + let mut edges: HashMap<(usize, usize), (Ptr, usize)> = HashMap::new(); + for &f in &faces { + let face = f; + for edge_num in 0..3 { + let v0 = face.v[edge_num]; + let v1 = face.v[next(edge_num)]; + let key = canonical_key(v0, v1); + + if let Some(&(first_face, first_edge_num)) = edges.get(&key) { + first_face.get_mut().unwrap().f[first_edge_num] = f; + f.get_mut().f[edge_num] = first_face; + edges.remove(&key); + } else { + edges.insert(key, (f, edge_num)); + } + } + } + + for &v in &vertices { + let vertex = v.get_mut().unwrap(); + let start = vertex.start_face; + let mut f = start; + let mut is_boundary = false; + loop { + let nf = f.next_face(v); + if nf.is_null() { + is_boundary = true; + break; + } + f = nf; + if f == start { + break; + } + } + + let val = valence(v); + vertex.boundary = is_boundary; + vertex.regular = if !is_boundary && val == 6 { + true + } else if is_boundary && val == 4 { + true + } else { + false + }; + } + + // -- Subdivision levels --------------------------------------------------- + let mut f = faces; + let mut v = vertices; + + for _ in 0..n_levels { + let mut new_faces: Vec> = Vec::new(); + let mut new_vertices: Vec> = Vec::new(); + + // Allocate vertex children + for &vertex_ptr in &v { + let vertex = vertex_ptr; + let child = arena.alloc(SDVertex { + p: Point3f::default(), + start_face: Ptr::null(), + child: Ptr::null(), + regular: vertex.regular, + boundary: vertex.boundary, + }); + vertex_ptr.get_mut().unwrap().child = child; + new_vertices.push(child); + } + + for &face_ptr in &f { + for k in 0..4 { + let child = arena.alloc(SDFace::new(Ptr::null(), Ptr::null(), Ptr::null())); + face_ptr.get_mut().unwrap().children[k] = child; + new_faces.push(child); + } + } + + for &vertex_ptr in &v { + let vertex = vertex_ptr; + let child = vertex.child; + let new_p = if !vertex.boundary { + if vertex.regular { + weight_one_ring(vertex_ptr, 1.0 / 16.0) + } else { + let val = valence(vertex_ptr); + weight_one_ring(vertex_ptr, beta(val)) + } + } else { + weight_boundary(vertex_ptr, 1.0 / 8.0) + }; + child.get_mut().unwrap().p = new_p; + } + + let mut edge_verts: HashMap<(usize, usize), Ptr> = HashMap::new(); + for &face_ptr in &f { + let face = face_ptr; + for k in 0..3 { + let v0 = face.v[k]; + let v1 = face.v[next(k)]; + let key = canonical_key(v0, v1); + + if !edge_verts.contains_key(&key) { + let is_boundary = face.f[k].is_null(); + let start_face = face.children[3]; + + let new_p = if is_boundary { + let p0 = v0.p; + let p1 = v1.p; + p0 * 0.5 + p1 * 0.5 + } else { + let p0 = v0.p; + let p1 = v1.p; + let p2 = face.other_vert(v0, v1).p; + let f2 = face.f[k]; + let p3 = f2.other_vert(v0, v1).p; + p0 * (3.0 / 8.0) + p1 * (3.0 / 8.0) + p2 * (1.0 / 8.0) + p3 * (1.0 / 8.0) + }; + + let vert = arena.alloc(SDVertex { + p: new_p, + start_face, + child: Ptr::null(), + regular: true, + boundary: is_boundary, + }); + edge_verts.insert(key, vert); + new_vertices.push(vert); + } + } + } + + for &vertex_ptr in &v { + let vertex = vertex_ptr; + let start_face = vertex.start_face; + let vert_num = start_face.vnum(vertex_ptr); + let child = vertex.child; + let child_start_face = start_face.children[vert_num]; + child.get_mut().unwrap().start_face = child_start_face; + } + + for &face_ptr in &f { + let face = face_ptr; + // Copy the data we need so we don't hold a borrow into the parent + // while we mutably borrow its children. + let face_children = face.children; + let face_f = face.f; + let face_v = face.v; + + for j in 0..3 { + let c3 = face_children[3]; + let c_j = face_children[j]; + let c_next_j = face_children[next(j)]; + + // Sibling links + if !c3.is_null() { + c3.get_mut().unwrap().f[j] = c_next_j; + } + if !c_j.is_null() { + c_j.get_mut().unwrap().f[next(j)] = c3; + } + + // Neighbor links + let f2 = face_f[j]; + if !f2.is_null() { + let f2_vnum = f2.vnum(face_v[j]); + let f2_child = f2.children[f2_vnum]; + if !c_j.is_null() { + c_j.get_mut().unwrap().f[j] = f2_child; + } + } else if !c_j.is_null() { + c_j.get_mut().unwrap().f[j] = Ptr::null(); + } + + let f2_prev = face_f[prev(j)]; + if !f2_prev.is_null() { + let f2_vnum = f2_prev.vnum(face_v[j]); + let f2_child = f2_prev.children[f2_vnum]; + if !c_j.is_null() { + c_j.get_mut().unwrap().f[prev(j)] = f2_child; + } + } else if !c_j.is_null() { + c_j.get_mut().unwrap().f[prev(j)] = Ptr::null(); + } + } + } + + // Update face vertex pointers + for &face_ptr in &f { + let face = face_ptr; + let face_v = face.v; + let face_children = face.children; + + for j in 0..3 { + let v_j_child = face_v[j].child; + let c_j = face_children[j]; + if !c_j.is_null() { + c_j.get_mut().unwrap().v[j] = v_j_child; + } + + let key = canonical_key(face_v[j], face_v[next(j)]); + let vert = edge_verts[&key]; + + let c_next_j = face_children[next(j)]; + let c3 = face_children[3]; + + if !c_j.is_null() { + c_j.get_mut().unwrap().v[next(j)] = vert; + } + if !c_next_j.is_null() { + c_next_j.get_mut().unwrap().v[j] = vert; + } + if !c3.is_null() { + c3.get_mut().unwrap().v[j] = vert; + } + } + } + + // Prepare for next level + f = new_faces; + v = new_vertices; + } + + let mut p_limit: Vec = Vec::with_capacity(v.len()); + for &vertex_ptr in &v { + let vertex = vertex_ptr; + if vertex.boundary { + p_limit.push(weight_boundary(vertex_ptr, 1.0 / 5.0)); + } else { + let val = valence(vertex_ptr); + p_limit.push(weight_one_ring(vertex_ptr, loop_gamma(val))); + } + } + for (i, &vertex_ptr) in v.iter().enumerate() { + vertex_ptr.get_mut().unwrap().p = p_limit[i]; + } + + let mut ns: Vec = Vec::with_capacity(v.len()); + let mut p_ring: Vec = Vec::with_capacity(16); + + for &vertex_ptr in &v { + let vertex = vertex_ptr; + let valence = valence(vertex_ptr); + p_ring.resize(valence, Point3f::default()); + one_ring(vertex_ptr, &mut p_ring[..valence]); + + let mut s = Vector3f::default(); + let mut t = Vector3f::default(); + + if !vertex.boundary { + for j in 0..valence { + let angle = 2.0 * std::f64::consts::PI * j as f64 / valence as f64; + s += Vector3f::from(p_ring[j]) * angle.cos() as Float; + t += Vector3f::from(p_ring[j]) * angle.sin() as Float; + } + } else { + s = Vector3f::from(p_ring[valence - 1] - p_ring[0]); + if valence == 2 { + t = Vector3f::from(p_ring[0] + p_ring[1] - vertex_ptr.p * 2.0); + } else if valence == 3 { + t = Vector3f::from(p_ring[1] - vertex_ptr.p); + } else if valence == 4 { + t = Vector3f::from( + p_ring[0] * -1.0 + + p_ring[1] * 2.0 + + p_ring[2] * 2.0 + + p_ring[3] * -1.0 + + vertex_ptr.p * -2.0, + ); + } else { + let theta = std::f64::consts::PI / (valence - 1) as f64; + t = Vector3f::from(p_ring[0] + p_ring[valence - 1].into()) * theta.sin() as Float; + for k in 1..(valence - 1) { + let wt = (2.0 * theta.cos() - 2.0) * ((k as f64) * theta).sin(); + t += Vector3f::from(p_ring[k]) * wt as Float; + } + t = -t; + } + } + ns.push(Normal3f::from(Vector3f::cross(s, t))); + } + + let ntris = f.len(); + let mut verts: Vec = Vec::with_capacity(3 * ntris); + let mut used_verts: HashMap, usize> = HashMap::new(); + for (i, &vertex_ptr) in v.iter().enumerate() { + used_verts.insert(vertex_ptr, i); + } + for &face_ptr in &f { + let face = face_ptr; + for j in 0..3 { + verts.push(used_verts[&face.v[j]]); + } + } + + TriangleMesh::new( + render_from_object, + reverse_orientation, + &verts, + &p_limit, + &[], + &ns, + &[], + &[], + ) +} diff --git a/src/utils/mod.rs b/src/utils/mod.rs index c1f0417..78ab32a 100644 --- a/src/utils/mod.rs +++ b/src/utils/mod.rs @@ -4,6 +4,7 @@ pub mod containers; pub mod error; pub mod file; pub mod io; +pub mod loopsubdiv; pub mod mipmap; pub mod parallel; pub mod parameters; @@ -12,11 +13,12 @@ pub mod upload; pub use error::FileLoc; pub use file::{read_float_file, resolve_filename}; +pub use loopsubdiv::*; +pub use mipmap::{MIPMap, MIPMapFilterOptions}; pub use parameters::{ ParameterDictionary, ParsedParameter, ParsedParameterVector, TextureParameterDictionary, }; -pub use mipmap::{MIPMap, MIPMapFilterOptions}; -pub use upload::{Upload, ArenaUpload}; +pub use upload::{ArenaUpload, Upload}; #[cfg(feature = "vulkan")] pub type Arena = arena::Arena; diff --git a/src/utils/parser.rs b/src/utils/parser.rs index 0bbc9ef..f97fe80 100644 --- a/src/utils/parser.rs +++ b/src/utils/parser.rs @@ -112,7 +112,7 @@ pub trait ParserTarget { loc: FileLoc, ) -> Result<(), ParserError>; - fn world_begin(&mut self, loc: FileLoc, arena: Arc) -> Result<(), ParserError>; + fn world_begin(&mut self, loc: FileLoc, arena: &Arena) -> Result<(), ParserError>; fn attribute_begin(&mut self, loc: FileLoc) -> Result<(), ParserError>; fn attribute_end(&mut self, loc: FileLoc) -> Result<(), ParserError>; fn attribute( @@ -576,7 +576,7 @@ impl ParserTarget for FormattingParserTarget { Ok(()) } - fn world_begin(&mut self, _loc: FileLoc, _arena: Arc) -> Result<(), ParserError> { + fn world_begin(&mut self, _loc: FileLoc, _arena: &Arena) -> Result<(), ParserError> { println!("{}WorldBegin", self.indent(0)); self.cat_indent_count += 4; Ok(()) @@ -976,8 +976,7 @@ impl<'a> SceneParser<'a> { } } - pub fn run(&mut self) -> Result<(), ParserError> { - let arena = Arc::new(Arena::default()); + pub fn run(&mut self, arena: Arc) -> Result<(), ParserError> { loop { let token = match self.next_token()? { Some(t) => t, @@ -1264,7 +1263,7 @@ impl<'a> SceneParser<'a> { }, 'W' => match token.text.as_str() { - "WorldBegin" => self.target.world_begin(token.loc, arena.clone())?, + "WorldBegin" => self.target.world_begin(token.loc, &arena)?, "WorldEnd" => {} _ => { return Err(ParserError::Generic(