Bunch of logic errors in port of math and sampling functions

This commit is contained in:
Wito Wiala 2026-09-02 17:21:16 +01:00
parent 28bb963268
commit 34ea80c030
17 changed files with 162 additions and 123 deletions

View file

@ -251,7 +251,7 @@ impl TabulatedBSSRDF {
sr += weight
* self
.table
.eval_profile(rho_offset + j as u32, radius_offset + k as u32);
.eval_profile((rho_offset + j as i32) as u32, (radius_offset + k as i32) as u32);
}
}
}
@ -302,14 +302,14 @@ impl TabulatedBSSRDF {
for (j, rho_weight) in rho_weights.iter().enumerate() {
if *rho_weight != 0. {
// Update _rhoEff_ and _sr_ for wavelength
rho_eff += rhoeff_samples[rho_offset as usize + j] * rho_weight;
rho_eff += rhoeff_samples[(rho_offset + j as i32) as usize] * rho_weight;
// Fix: Use .iter().enumerate() for 'k'
for (k, radius_weight) in radius_weights.iter().enumerate() {
if *radius_weight != 0. {
sr += self
.table
.eval_profile(rho_offset + j as u32, radius_offset + k as u32)
.eval_profile((rho_offset + j as i32) as u32, (radius_offset + k as i32) as u32)
* rho_weight
* radius_weight;
}

View file

@ -625,7 +625,8 @@ impl RGBSigmoidPolynomial {
}
pub fn evaluate(&self, lambda: Float) -> Float {
let eval = evaluate_polynomial(lambda, &[self.c0, self.c1, self.c2]);
// pbrt: `s(EvaluatePolynomial(lambda, c2, c1, c0))` -- c2 is the constant term.
let eval = evaluate_polynomial(lambda, &[self.c2, self.c1, self.c0]);
Self::s(eval)
}

View file

@ -192,15 +192,18 @@ impl SamplerTrait for HaltonSampler {
}
fn get1d(&mut self) -> Float {
if self.dim + 1 >= PRIME_TABLE_SIZE as u32 {
// pbrt: `SampleDimension(dimension++)` -- POST-increment. Pre-incrementing makes
// the next Get2D() reuse the dimension this call just consumed.
if self.dim >= PRIME_TABLE_SIZE as u32 {
self.dim = 2;
}
let dim = self.dim;
self.dim += 1;
self.sample_dimension(self.dim)
self.sample_dimension(dim)
}
fn get2d(&mut self) -> Point2f {
if self.dim > PRIME_TABLE_SIZE as u32 {
if self.dim + 1 >= PRIME_TABLE_SIZE as u32 {
self.dim = 2;
}
let dim = self.dim;
@ -301,8 +304,9 @@ impl SamplerTrait for StratifiedSampler {
hash as u32,
);
self.dim += 2;
// pbrt: both the modulus and the divisor are xPixelSamples.
let x = stratum % self.x_pixel_samples as u32;
let y = stratum / self.y_pixel_samples as u32;
let y = stratum / self.x_pixel_samples as u32;
let dx = if self.jitter {
self.rng.uniform::<Float>()
} else {
@ -387,6 +391,7 @@ impl SamplerTrait for PaddedSobolSampler {
self.samples_per_pixel as u32,
hash as u32,
);
self.dim += 1;
self.sample_dimension(0, index, (hash >> 32) as u32)
}
fn get2d(&mut self) -> Point2f {
@ -512,7 +517,7 @@ impl SamplerTrait for SobolSampler {
) as Float;
u[1] = clamp(
u[1] * self.scale as Float - self.pixel[1] as Float,
1.,
0.,
ONE_MINUS_EPSILON,
) as Float;
u

View file

@ -1,8 +1,8 @@
use crate::core::geometry::{spherical_triangle_area, SqrtExt, Tuple, VectorLike};
use crate::core::geometry::{
Bounds3f, DirectionCone, Normal, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3,
Vector3f,
};
use crate::core::geometry::{SqrtExt, Tuple, VectorLike, spherical_triangle_area};
use crate::core::interaction::{
Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction,
};
@ -13,7 +13,7 @@ use crate::utils::sampling::{
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
sample_uniform_triangle,
};
use crate::{gamma, Float, GVec, Ptr};
use crate::{Float, GVec, Ptr, gamma};
#[repr(C)]
#[derive(Debug, Clone, Copy)]
@ -198,34 +198,34 @@ impl TriangleShape {
// Ensure that computed triangle $t$ is conservatively greater than zero
// Compute $\delta_z$ term for triangle $t$ error bounds
let maxZt = Vector3f::new(p0t.z(), p1t.z(), p2t.z())
let max_zt = Vector3f::new(p0t.z(), p1t.z(), p2t.z())
.abs()
.max_component_value();
let deltaZ = gamma(3) * maxZt;
let delta_z = gamma(3) * max_zt;
// Compute $\delta_x$ and $\delta_y$ terms for triangle $t$ error bounds
let maxXt = Vector3f::new(p0t.x(), p1t.x(), p2t.x())
let max_xt = Vector3f::new(p0t.x(), p1t.x(), p2t.x())
.abs()
.max_component_value();
let maxYt = Vector3f::new(p0t.y(), p1t.y(), p2t.y())
let max_yt = Vector3f::new(p0t.y(), p1t.y(), p2t.y())
.abs()
.max_component_value();
let deltaX = gamma(5) * (maxXt + maxZt);
let deltaY = gamma(5) * (maxYt + maxZt);
let delta_x = gamma(5) * (max_xt + max_zt);
let delta_y = gamma(5) * (max_yt + max_zt);
// Compute $\delta_e$ term for triangle $t$ error bounds
let deltaE = 2. * (gamma(2) * maxXt * maxYt + deltaY * maxXt + deltaX * maxYt);
let delta_e = 2. * (gamma(2) * max_xt * max_yt + delta_y * max_xt + delta_x * max_yt);
// Compute $\delta_t$ term for triangle $t$ error bounds and check _t_
let maxE = Vector3f::new(e0, e1, e2).abs().max_component_value();
let deltaT =
3. * (gamma(3) * maxE * maxZt + deltaE * maxZt + deltaZ * maxE) * inv_det.abs();
if t <= deltaT {
let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value();
let delta_t =
3. * (gamma(3) * max_e * max_zt + delta_e * max_zt + delta_z * max_e) * inv_det.abs();
if t <= delta_t {
return None;
}
// Return _TriangleIntersection_ for intersection
return Some(TriangleIntersection { b0, b1, b2, t });
Some(TriangleIntersection { b0, b1, b2, t })
}
fn interaction_from_intersection(
@ -345,11 +345,11 @@ impl TriangleShape {
let mut ts = ns.cross(ss.into());
if ts.norm_squared() > 0.0 {
ss = ts.cross(ns.into()).into();
ss = ts.cross(ns).into();
} else {
let (s, t) = ns.coordinate_system();
ss = s.into();
ts = t.into();
ts = t;
}
let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() {
@ -539,7 +539,8 @@ impl ShapeTrait for TriangleShape {
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
let [p0, p1, p2] = self.get_points();
let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY), p0, p1, p2)?;
let tri_isect =
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY), p0, p1, p2)?;
let intr = self.interaction_from_intersection(tri_isect, ray.time, -ray.d);
Some(ShapeIntersection::new(intr, tri_isect.t))
}

View file

@ -61,11 +61,14 @@ where
T::lerp(t, a, b)
}
/// pbrt `EvaluatePolynomial(t, c, cRemaining...) = FMA(t, EvaluatePolynomial(t, cRemaining...), c)`.
/// `coeffs[0]` is the *constant* term and `coeffs[n-1]` the highest-degree one, so
/// evaluation folds from the back.
#[inline]
pub fn evaluate_polynomial(t: Float, coeffs: &[Float]) -> Float {
assert!(!coeffs.is_empty());
let mut result = coeffs[0];
for &c in &coeffs[1..] {
let mut result = coeffs[coeffs.len() - 1];
for &c in coeffs[..coeffs.len() - 1].iter().rev() {
result = num_traits::Float::mul_add(t, result, c);
}
result
@ -96,7 +99,7 @@ where
#[inline]
pub fn safe_sqrt(x: Float) -> Float {
assert!(x > -1e-3);
debug_assert!(x > -1e-3);
0.0_f32.max(x).sqrt()
}
@ -104,20 +107,14 @@ pub fn safe_sqrt(x: Float) -> Float {
pub fn safe_asin<T: NumFloat>(x: T) -> T {
let epsilon = T::from(0.0001).unwrap();
let one = T::one();
if x >= -(one + epsilon) && x <= one + epsilon {
debug_assert!(x >= -(one + epsilon) && x <= one + epsilon);
clamp(x, -one, one).asin()
} else {
panic!("Not valid value for asin")
}
}
#[inline]
pub fn safe_acos(x: Float) -> Float {
if (-1.001..1.001).contains(&x) {
debug_assert!((-1.001..1.001).contains(&x));
clamp(x, -1., 1.).acos()
} else {
panic!("Not valid value for acos")
}
}
#[inline]
@ -351,9 +348,10 @@ pub fn wrap_equal_area_square(uv: &mut Point2f) -> Point2f {
pub fn integrate_catmull_rom(nodes: &[Float], f: &[Float], cdf: &mut [Float]) -> Float {
debug_assert_eq!(nodes.len(), f.len());
debug_assert_eq!(cdf.len(), nodes.len());
let mut sum = 0.;
cdf[0] = 0.;
for i in 0..nodes.len() {
for i in 0..nodes.len() - 1 {
let x0 = nodes[i];
let x1 = nodes[i + 1];
let f0 = f[i];
@ -434,7 +432,7 @@ pub fn invert_catmull_rom(nodes: &[Float], f: &[Float], u: Float) -> Float {
return x0 + t * width;
}
pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4])> {
pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(i32, [Float; 4])> {
if nodes.len() < 4 {
return None;
}
@ -452,7 +450,10 @@ pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4]
// Safety clamp (though bounds check above handles most cases)
let idx = idx.min(nodes.len() - 2);
let offset = idx.saturating_sub(1); // The C++ code uses idx - 1 for the offset
// pbrt: `*offset = idx - 1`, which is legitimately -1 when idx == 0. weights[0] is
// then 0, so callers never dereference the out-of-range slot -- but the remaining
// three weights must still line up with nodes[idx-1+i].
let offset = idx as i32 - 1;
let x0 = nodes[idx];
let x1 = nodes[idx + 1];
@ -491,7 +492,7 @@ pub fn catmull_rom_weights(nodes: &[Float], x: Float) -> Option<(u32, [Float; 4]
weights[3] = 0.0;
}
Some((offset as u32, weights))
Some((offset, weights))
}
pub fn equal_area_sphere_to_square(d: Vector3f) -> Point2f {
@ -546,7 +547,7 @@ pub fn equal_area_square_to_sphere(p: Point2f) -> Vector3f {
// Compute angle \phi for square to sphere mapping
let mut phi = if r == 0. { 1. } else { (vp - up) / r + 1. };
phi /= PI_OVER_4;
phi *= PI_OVER_4;
// Find z for spherical direction
let z = (1. - square(r)).copysign(signed_distance);
@ -585,12 +586,13 @@ pub fn gaussian_integral(x0: Float, x1: Float, mu: Float, sigma: Float) -> Float
0.5 * (erf((mu - x0) / sigma_root2) - erf((mu - x1) / sigma_root2))
}
#[inline(always)]
pub fn sample_linear(u: Float, a: Float, b: Float) -> Float {
assert!(a >= 0. && b >= 0.);
if u == 0. && a == 0. {
return 0.;
}
let x = u * (a + b) / (a + (lerp(u, square(a), square(b))));
let x = u * (a + b) / (a + lerp(u, square(a), square(b)).sqrt());
x.min(ONE_MINUS_EPSILON)
}
@ -700,12 +702,13 @@ pub fn sample_discrete(
}
pub fn sample_tent(u: Float, r: Float) -> Float {
// pbrt rewrites `u` in place with the remapped sample and feeds *that* to SampleLinear.
let mut u_remapped = 0.0;
let offset = sample_discrete(&[0.5, 0.5], u, None, Some(&mut u_remapped));
if offset == 0 {
-r + r * sample_linear(u, 0., 1.)
-r + r * sample_linear(u_remapped, 0., 1.)
} else {
r * sample_linear(u, 1., 0.)
r * sample_linear(u_remapped, 1., 0.)
}
}
@ -735,7 +738,7 @@ pub fn encode_morton_2(x: u32, y: u32) -> u64 {
}
pub fn encode_morton_3(x: Float, y: Float, z: Float) -> u32 {
(left_shift3(x as u32) << 2) | (left_shift3(y as u32) << 1) | left_shift3(z as u32)
(left_shift3(z as u32) << 2) | (left_shift3(y as u32) << 1) | left_shift3(x as u32)
}
pub fn round_up_pow2(mut n: i32) -> i32 {
@ -1590,7 +1593,7 @@ where
}
let det: T = (0..N).map(|i| lum[i][i]).product();
if parity < 0 { -det } else { det }
if parity % 2 == 1 { -det } else { det }
}
}
}
@ -1723,7 +1726,9 @@ pub fn f16_to_f32_software(h: u16) -> f32 {
m <<= 1;
e += 1;
}
(sign << 31) | ((112 - e) << 23) | ((m & 0x3FF) << 13)
// half subnormals have exponent -14; after `e` normalising shifts the
// f32 biased exponent is (-14 - e) + 127 = 113 - e.
(sign << 31) | ((113 - e) << 23) | ((m & 0x3FF) << 13)
}
} else if exp == 0x1F {
(sign << 31) | (0xFF << 23) | (mant << 13)

View file

@ -52,7 +52,7 @@ pub fn invert_linear_sample(x: Float, a: Float, b: Float) -> Float {
pub fn sample_bilinear(u: Point2f, w: &[Float]) -> Point2f {
let y = sample_linear(u[1], w[0] + w[1], w[2] + w[3]);
let x = sample_linear(u[1], lerp(y, w[0], w[2]), lerp(y, w[1], w[3]));
let x = sample_linear(u[0], lerp(y, w[0], w[2]), lerp(y, w[1], w[3]));
Point2f::new(x, y)
}
@ -333,7 +333,8 @@ pub fn invert_spherical_rectangle_sample(
}
pub fn sample_exponential(u: Float, a: Float) -> Float {
(1. - u).ln() / a
debug_assert!(a > 0.);
-(1. - u).ln() / a
}
pub fn sample_spherical_triangle(
@ -550,7 +551,9 @@ pub fn sample_catmull_rom_2d(
let mut v = 0.;
for i in 0..4 {
if weights[i] != 0. {
let ind = (offset + i as u32) * n2 + idx;
// `offset` is pbrt's `idx - 1` and may be -1; the matching weight is
// then zero, so the slot is never actually read.
let ind = (offset + i as i32) * n2 as i32 + idx as i32;
v += array[ind as usize] * weights[i];
}
}
@ -658,10 +661,10 @@ pub struct VarianceEstimator {
impl VarianceEstimator {
pub fn add(&mut self, x: Float) {
self.n += 1;
let delta = x / self.mean;
let delta = x - self.mean;
self.mean += delta / self.n as Float;
let delta2 = x - self.mean;
self.s *= delta * delta2;
self.s += delta * delta2;
}
pub fn mean(&self) -> Float {
@ -688,7 +691,7 @@ impl VarianceEstimator {
if ve.n != 0 {
self.s += ve.s
+ square(ve.mean - self.mean) * (self.n * ve.n) as Float / (self.n + ve.n) as Float;
self.mean +=
self.mean =
(self.n as Float * self.mean + ve.n as Float * ve.mean) / (self.n + ve.n) as Float;
self.n += ve.n;
}
@ -719,24 +722,37 @@ impl PiecewiseConstant1D {
}
pub fn new_with_bounds(f: &[Float], min: Float, max: Float) -> Self {
debug_assert!(max > min);
let n = f.len();
// pbrt takes the absolute value of the function before integrating, so filters
// with negative lobes (Mitchell, LanczosSinc) still give a usable CDF.
let mut func: GVec<Float> = gvec_with_capacity(n);
for &v in f {
func.push(v.abs());
}
let mut cdf = gvec_with_capacity(n + 1);
cdf.push(0.0);
let delta = (max - min) / n as Float;
for i in 0..n {
cdf.push(cdf[i] + f[i] * delta);
cdf.push(cdf[i] + func[i] * delta);
}
let func_integral = cdf[n];
if func_integral > 0.0 {
for c in &mut cdf {
if func_integral == 0.0 {
for (i, c) in cdf.iter_mut().enumerate().skip(1) {
*c = i as Float / n as Float;
}
} else {
for c in cdf.iter_mut().skip(1) {
*c /= func_integral;
}
}
Self {
func: gvec_from_slice(f),
func,
cdf,
min,
max,
@ -748,7 +764,6 @@ impl PiecewiseConstant1D {
where
F: Fn(Float) -> Float,
{
let delta = (max - min) / n as Float;
let delta = (max - min) / n as Float;
let mut values = gvec_with_capacity(n);
for i in 0..n {
@ -816,6 +831,7 @@ impl PiecewiseConstant1D {
pub struct PiecewiseConstant2D {
pub conditionals: GVec<PiecewiseConstant1D>,
pub marginal: PiecewiseConstant1D,
pub domain: Bounds2f,
pub n_u: u32,
pub n_v: u32,
}
@ -852,6 +868,7 @@ impl PiecewiseConstant2D {
Self {
conditionals,
marginal,
domain,
n_u: n_u.try_into().unwrap(),
n_v: n_v.try_into().unwrap(),
}
@ -876,7 +893,10 @@ impl PiecewiseConstant2D {
self.marginal.integral()
}
pub fn pdf(&self, p: Point2f) -> Float {
pub fn pdf(&self, pr: Point2f) -> Float {
// pbrt maps the query point into the unit domain first; the distribution's
// domain is only [0,1]^2 for image distributions, not e.g. filter samplers.
let p = Point2f::from(self.domain.offset(&pr));
let u_offset = ((p.x() * self.n_u as Float) as usize).min(self.n_u as usize - 1);
let v_offset = ((p.y() * self.n_v as Float) as usize).min(self.n_v as usize - 1);
let conditional = unsafe { &*self.conditionals.as_ptr().add(v_offset) };

View file

@ -81,16 +81,15 @@ impl MaterialFactory for Material {
SubsurfaceMaterial::create(parameters, normal_map, named_materials, &loc, arena)
}
"mix" => MixMaterial::create(parameters, normal_map, named_materials, &loc, arena),
_ => Err(anyhow!("Material type '{}' unknown at {}", name, &loc)),
}
}
}
pub fn default_diffuse_material(arena: &Arena) -> Material {
use shared::core::texture::SpectrumTexture;
use shared::core::texture::SpectrumConstantTexture;
use shared::core::spectrum::{ConstantSpectrum, Spectrum};
use shared::core::texture::SpectrumConstantTexture;
use shared::core::texture::SpectrumTexture;
use shared::materials::DiffuseMaterial;
use shared::utils::Ptr;

View file

@ -24,10 +24,7 @@ impl CreateMaterial for CoatedDiffuseMaterial {
arena: &Arena,
) -> Result<Material> {
let reflectance = parameters
.get_spectrum_texture("reflectance", None, SpectrumType::Albedo)
.unwrap_or(Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))),
)));
.get_spectrum_texture("reflectance", 0.5, SpectrumType::Albedo);
let u_roughness =
parameters.get_float_texture_with_fallback("uroughness", "roughness", 0.5)?;
@ -48,11 +45,7 @@ impl CreateMaterial for CoatedDiffuseMaterial {
let n_samples = parameters.get_one_int("nsamples", 1)?;
let g = parameters.get_float_texture("g", 0.)?;
let albedo = parameters
.get_spectrum_texture("albedo", None, SpectrumType::Albedo)
.unwrap_or_else(|| {
let default_spectrum = Spectrum::Constant(ConstantSpectrum::new(0.));
SpectrumTexture::Constant(SpectrumConstantTexture::new(default_spectrum)).into()
});
.get_spectrum_texture("albedo", 0., SpectrumType::Albedo);
let displacement = parameters.get_float_texture("displacement", 0.)?;
let remap_roughness = parameters.get_one_bool("remaproughness", true)?;
@ -146,11 +139,7 @@ impl CreateMaterial for CoatedConductorMaterial {
let n_samples = parameters.get_one_int("nsamples", 1)?;
let g = parameters.get_float_texture("g", 0.)?;
let albedo = parameters
.get_spectrum_texture("albedo", None, SpectrumType::Albedo)
.unwrap_or_else(|| {
let spectrum = Spectrum::Constant(ConstantSpectrum::new(0.));
SpectrumTexture::Constant(SpectrumConstantTexture::new(spectrum)).into()
});
.get_spectrum_texture("albedo", 0., SpectrumType::Albedo);
let displacement = parameters.get_float_texture_or_null("displacement")?;
let remap_roughness = parameters.get_one_bool("remaproughness", true)?;

View file

@ -240,8 +240,7 @@ impl CreateMaterial for SubsurfaceMaterial {
Some(r) => {
let one = Spectrum::Constant(ConstantSpectrum::new(1.));
let mfp = parameters
.get_spectrum_texture("mfp", Some(one), SpectrumType::Unbounded)
.expect("default supplied");
.get_spectrum_texture("mfp", one, SpectrumType::Unbounded);
SubsurfaceScattering::Reflectance {
reflectance: arena.upload(r),
mfp: arena.upload(mfp),

View file

@ -52,7 +52,7 @@ impl CreateMaterial for ThinDielectricMaterial {
parameters: &TextureParameterDictionary,
normal_map: Option<Arc<HostImage>>,
_named_materials: &HashMap<String, Material>,
loc: &FileLoc,
_loc: &FileLoc,
arena: &Arena,
) -> Result<Material> {
let eta = if let Some(&v) = parameters.get_float_array("eta")?.first() {

View file

@ -1,9 +1,9 @@
use crate::Arena;
use crate::core::image::HostImage;
use crate::core::material::CreateMaterial;
use crate::core::texture::SpectrumTexture;
use crate::utils::upload::ArenaUpload;
use crate::utils::{FileLoc, TextureParameterDictionary};
use crate::Arena;
use anyhow::Result;
use shared::core::material::Material;
use shared::core::spectrum::Spectrum;
@ -22,13 +22,7 @@ impl CreateMaterial for DiffuseMaterial {
_loc: &FileLoc,
arena: &Arena,
) -> Result<Material> {
let reflectance = parameters
.get_spectrum_texture("reflectance", None, SpectrumType::Albedo)
.unwrap_or_else(|| {
Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(Spectrum::Constant(ConstantSpectrum::new(0.5))),
))
});
let reflectance = parameters.get_spectrum_texture("reflectance", 0.5, SpectrumType::Albedo);
let displacement = parameters.get_float_texture_or_null("displacement")?;
let specific = DiffuseMaterial {
@ -42,12 +36,26 @@ impl CreateMaterial for DiffuseMaterial {
impl CreateMaterial for DiffuseTransmissionMaterial {
fn create(
_parameters: &TextureParameterDictionary,
_normal_map: Option<Arc<HostImage>>,
parameters: &TextureParameterDictionary,
normal_map: Option<Arc<HostImage>>,
_named_materials: &HashMap<String, Material>,
_loc: &FileLoc,
_arena: &Arena,
arena: &Arena,
) -> Result<Material> {
todo!()
let reflectance =
parameters.get_spectrum_texture("reflectance", 0.25, SpectrumType::Albedo);
let transmittance =
parameters.get_spectrum_texture("transmittance", 0.25, SpectrumType::Albedo);
let displacement = parameters.get_float_texture_or_null("displacement")?;
let scale = parameters.get_one_float("scale", 1.)?;
let mat = DiffuseTransmissionMaterial {
normal_map: arena.upload(normal_map),
displacement: arena.upload(displacement),
reflectance: arena.upload(reflectance),
transmittance: arena.upload(transmittance),
scale,
};
Ok(Material::DiffuseTransmission(mat))
}
}

View file

@ -1,7 +1,9 @@
use crate::core::image::HostImage;
use crate::core::material::CreateMaterial;
use crate::utils::{Arena, FileLoc, TextureParameterDictionary};
use crate::{Arena, ArenaUpload, FileLoc, utils::TextureParameterDictionary};
use anyhow::Result;
use anyhow::anyhow;
use shared::Ptr;
use shared::core::material::Material;
use shared::materials::MixMaterial;
use std::collections::HashMap;
@ -9,12 +11,31 @@ use std::sync::Arc;
impl CreateMaterial for MixMaterial {
fn create(
_parameters: &TextureParameterDictionary,
parameters: &TextureParameterDictionary,
_normal_map: Option<Arc<HostImage>>,
_named_materials: &HashMap<String, Material>,
_loc: &FileLoc,
_arena: &Arena,
named_materials: &HashMap<String, Material>,
loc: &FileLoc,
arena: &Arena,
) -> Result<Material> {
todo!()
let names = parameters.get_string_array("materials")?;
let [a, b]: [String; 2] = names
.try_into()
.map_err(|_| anyhow!("{loc}: must provide two values for \"string materials\""))?;
let mut resolve = |n: &str| -> Result<Ptr<Material>> {
let m = named_materials
.get(n)
.ok_or_else(|| anyhow!("{loc}: {n}: named material not found"))?;
Ok(arena.alloc(*m))
};
let materials = [resolve(&a)?, resolve(&b)?];
let amount = parameters.get_float_texture("amount", 0.5)?;
let mix_mat = MixMaterial {
materials,
amount: arena.upload(amount),
};
Ok(Material::Mix(mix_mat))
}
}

View file

@ -64,8 +64,7 @@ impl CreateSpectrumTexture for SpectrumCheckerboardTexture {
let tex = |name: &str, def: Spectrum| {
arena.upload(
parameters
.get_spectrum_texture(name, Some(def), spectrum_type)
.expect("default supplied"),
.get_spectrum_texture(name, def, spectrum_type),
)
};

View file

@ -47,8 +47,7 @@ impl CreateSpectrumTexture for SpectrumDotsTexture {
let get = |name: &str, def: Spectrum| {
let t = parameters
.get_spectrum_texture(name, Some(def), spectrum_type)
.expect("default supplied");
.get_spectrum_texture(name, def, spectrum_type);
arena.upload(t)
};
let tex = SpectrumDotsTexture::new(map, get("inside", one), get("outside", zero));

View file

@ -87,8 +87,7 @@ impl CreateSpectrumTexture for SpectrumMixTexture {
let tex = |name: &str, def: Spectrum| {
parameters
.get_spectrum_texture(name, Some(def), spectrum_type)
.expect("default supplied")
.get_spectrum_texture(name, def, spectrum_type)
};
let tex = SpectrumMixTexture {
@ -119,8 +118,7 @@ impl CreateSpectrumTexture for SpectrumDirectionMixTexture {
let dir = render_from_texture.apply_to_vector(dir_raw).normalize();
let tex = |name: &str, def: Spectrum| {
parameters
.get_spectrum_texture(name, Some(def), spectrum_type)
.expect("default supplied")
.get_spectrum_texture(name, def, spectrum_type)
};
let zero = Spectrum::Constant(ConstantSpectrum::new(0.));

View file

@ -66,9 +66,8 @@ impl CreateSpectrumTexture for SpectrumScaledTexture {
_arena: &Arena,
) -> Result<SpectrumTexture> {
let one = Spectrum::Constant(ConstantSpectrum::new(1.0));
let tex = parameters
.get_spectrum_texture("tex", Some(one), spectrum_type)
.ok_or_else(|| anyhow::anyhow!("{}: missing \"tex\" parameter", _loc))?;
// pbrt defaults "tex" to 1.0 rather than erroring (textures.cpp).
let tex = parameters.get_spectrum_texture("tex", one, spectrum_type);
let scale = parameters.get_float_texture("scale", 1.0)?;
if let FloatTexture::Constant(ref cscale) = *scale {

View file

@ -835,22 +835,18 @@ impl TextureParameterDictionary {
self.dict.report_unused()
}
/// Fetch a spectrum texture, falling back to a constant built from `def`.
/// A default is always supplied, so this cannot fail -- mirrors
/// `get_float_texture`. Use `get_spectrum_texture_or_null` when absence is
/// meaningful.
pub fn get_spectrum_texture(
&self,
name: &str,
val: Option<Spectrum>,
def: impl Into<Spectrum>,
stype: SpectrumType,
) -> Option<Arc<SpectrumTexture>> {
let tex = self.get_spectrum_texture_or_null(name, stype);
if tex.is_some() {
tex
} else if val.is_some() {
Some(Arc::new(SpectrumTexture::Constant(
SpectrumConstantTexture::new(val.unwrap()),
)))
} else {
None
}
) -> Arc<SpectrumTexture> {
self.get_spectrum_texture_or_null(name, stype)
.unwrap_or_else(|| Arc::new(SpectrumTexture::from(def.into())))
}
pub fn get_float_texture(&self, name: &str, val: Float) -> Result<Arc<FloatTexture>> {