244 lines
7.9 KiB
Rust
244 lines
7.9 KiB
Rust
use crate::core::geometry::{
|
|
Bounds3f, DirectionCone, Normal3f, Point2f, Point3f, Point3fi, Ray, Vector2f, Vector3f,
|
|
Vector3fi, VectorLike,
|
|
};
|
|
use crate::core::interaction::{Interaction, InteractionTrait, SurfaceInteraction};
|
|
use crate::core::shape::{
|
|
QuadricIntersection, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
|
|
};
|
|
use crate::utils::interval::Interval;
|
|
use crate::utils::math::{clamp, radians, square};
|
|
use crate::utils::sampling::sample_uniform_disk_concentric;
|
|
use crate::utils::Transform;
|
|
use crate::{Float, PI};
|
|
use num_traits::Float as NumFloat;
|
|
|
|
#[repr(C)]
|
|
#[derive(Debug, Clone, Copy)]
|
|
pub struct DiskShape {
|
|
pub radius: Float,
|
|
pub inner_radius: Float,
|
|
pub height: Float,
|
|
pub phi_max: Float,
|
|
pub render_from_object: Transform,
|
|
pub object_from_render: Transform,
|
|
pub reverse_orientation: bool,
|
|
pub transform_swap_handedness: bool,
|
|
}
|
|
|
|
impl DiskShape {
|
|
pub fn new(
|
|
radius: Float,
|
|
inner_radius: Float,
|
|
height: Float,
|
|
phi_max: Float,
|
|
render_from_object: Transform,
|
|
object_from_render: Transform,
|
|
reverse_orientation: bool,
|
|
) -> Self {
|
|
Self {
|
|
radius,
|
|
inner_radius,
|
|
height,
|
|
// pbrt: `phiMax(Radians(Clamp(phiMax, 0, 360)))`. The parameter arrives in
|
|
// DEGREES (default 360); storing it raw made `area()` 360/2pi = 57.3x too
|
|
// large, so the sampling pdf was 57.3x too small and every direct-lighting
|
|
// contribution from a disk area light was 57.3x too bright.
|
|
phi_max: radians(clamp(phi_max, 0., 360.)),
|
|
render_from_object: render_from_object.clone(),
|
|
object_from_render,
|
|
reverse_orientation,
|
|
transform_swap_handedness: render_from_object.swaps_handedness(),
|
|
}
|
|
}
|
|
|
|
fn basic_intersect(&self, r: &Ray, t_max: Float) -> Option<QuadricIntersection> {
|
|
let oi = self
|
|
.object_from_render
|
|
.apply_to_interval(&Point3fi::new_from_point(r.o));
|
|
let di = self
|
|
.object_from_render
|
|
.apply_to_vector_interval(&Vector3fi::new_from_vector(r.d));
|
|
|
|
if Float::from(di.z()) == 0. {
|
|
return None;
|
|
}
|
|
let t_shape_hit: Interval = (self.height - oi.z()) / di.z();
|
|
if t_shape_hit.high <= 0. || t_shape_hit.low >= t_max {
|
|
return None;
|
|
}
|
|
|
|
let oi_f = Point3f::from(oi);
|
|
let di_f = Vector3f::from(di);
|
|
let t = Float::from(t_shape_hit);
|
|
let p_hit: Point3f = oi_f + di_f * t;
|
|
|
|
let dist2 = square(p_hit.x()) + square(p_hit.y());
|
|
if dist2 > square(self.radius) || dist2 < square(self.inner_radius) {
|
|
return None;
|
|
}
|
|
let mut phi = p_hit.y().atan2(p_hit.x());
|
|
if phi < 0. {
|
|
phi += 2. * PI;
|
|
}
|
|
if phi > self.phi_max {
|
|
return None;
|
|
}
|
|
|
|
Some(QuadricIntersection {
|
|
t_hit: t,
|
|
p_obj: p_hit,
|
|
phi,
|
|
})
|
|
}
|
|
|
|
fn interaction_from_intersection(
|
|
&self,
|
|
isect: QuadricIntersection,
|
|
wo: Vector3f,
|
|
time: Float,
|
|
) -> SurfaceInteraction {
|
|
let mut p_hit = isect.p_obj;
|
|
let phi = isect.phi;
|
|
// Find parametric representation of disk hit
|
|
let u = phi / self.phi_max;
|
|
let r_hit = (square(p_hit.x()) + square(p_hit.y())).sqrt();
|
|
let v = (self.radius - r_hit) / (self.radius - self.inner_radius);
|
|
let dpdu = Vector3f::new(-self.phi_max * p_hit.y(), self.phi_max * p_hit.x(), 0.);
|
|
let dpdv =
|
|
Vector3f::new(p_hit.x(), p_hit.y(), 0.) * (self.inner_radius - self.radius) / r_hit;
|
|
let dndu = Normal3f::zero();
|
|
let dndv = Normal3f::zero();
|
|
|
|
p_hit[2] = self.height;
|
|
|
|
let p_error = Vector3f::zero();
|
|
let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
|
|
let wo_object = self.object_from_render.apply_to_vector(wo);
|
|
let intr = SurfaceInteraction::new(
|
|
Point3fi::new_with_error(p_hit, p_error),
|
|
Point2f::new(u, v),
|
|
wo_object,
|
|
dpdu,
|
|
dpdv,
|
|
dndu,
|
|
dndv,
|
|
time,
|
|
flip_normal,
|
|
);
|
|
|
|
match self
|
|
.render_from_object
|
|
.apply_to_interaction(&Interaction::Surface(intr))
|
|
{
|
|
Interaction::Surface(si) => si,
|
|
_ => unreachable!("Only surfaces need apply"),
|
|
}
|
|
}
|
|
}
|
|
|
|
impl ShapeTrait for DiskShape {
|
|
fn area(&self) -> Float {
|
|
self.phi_max * 0.5 * (square(self.radius) - square(self.inner_radius))
|
|
}
|
|
|
|
fn bounds(&self) -> Bounds3f {
|
|
self.render_from_object
|
|
.apply_to_bounds(Bounds3f::from_points(
|
|
Point3f::new(-self.radius, -self.radius, self.height),
|
|
Point3f::new(self.radius, self.radius, self.height),
|
|
))
|
|
}
|
|
fn normal_bounds(&self) -> DirectionCone {
|
|
let mut n = self
|
|
.render_from_object
|
|
.apply_to_normal(Normal3f::new(0., 0., 1.));
|
|
if self.reverse_orientation {
|
|
n = -n;
|
|
}
|
|
DirectionCone::new_from_vector(Vector3f::from(n))
|
|
}
|
|
|
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
|
let t = t_max.unwrap_or(Float::INFINITY);
|
|
if let Some(isect) = self.basic_intersect(ray, t) {
|
|
let intr = self.interaction_from_intersection(isect.clone(), -ray.d, ray.time);
|
|
Some(ShapeIntersection::new(intr, isect.t_hit))
|
|
} else {
|
|
None
|
|
}
|
|
}
|
|
|
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
|
let pd = sample_uniform_disk_concentric(u);
|
|
let p_obj = Point3f::new(pd.x() * self.radius, pd.y() * self.radius, self.height);
|
|
let pi = self
|
|
.render_from_object
|
|
.apply_to_interval(&Point3fi::new_from_point(p_obj));
|
|
let mut n: Normal3f = self
|
|
.render_from_object
|
|
.apply_to_normal(Normal3f::new(0., 0., 1.))
|
|
.normalize();
|
|
if self.reverse_orientation {
|
|
n = -n;
|
|
}
|
|
let mut phi = pd.y().atan2(pd.x());
|
|
if phi < 0. {
|
|
phi += 2. * PI;
|
|
}
|
|
let radius_sample = (square(p_obj.x()) + square(p_obj.y())).sqrt();
|
|
let uv = Point2f::new(
|
|
phi / self.phi_max,
|
|
(self.radius - radius_sample) / (self.radius - self.inner_radius),
|
|
);
|
|
|
|
Some(ShapeSample {
|
|
intr: Interaction::Surface(SurfaceInteraction::new_simple(pi, n, uv)),
|
|
pdf: 1. / self.area(),
|
|
})
|
|
}
|
|
|
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
|
if let Some(t) = t_max {
|
|
self.basic_intersect(ray, t).is_some()
|
|
} else {
|
|
self.basic_intersect(ray, Float::INFINITY).is_some()
|
|
}
|
|
}
|
|
|
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
|
let mut ss = self.sample(u)?;
|
|
ss.intr.get_common_mut().time = ctx.time;
|
|
let mut wi = ss.intr.p() - ctx.p();
|
|
if wi.norm_squared() == 0. {
|
|
return None;
|
|
}
|
|
wi = wi.normalize();
|
|
|
|
ss.pdf /= Vector3f::from(ss.intr.n()).abs_dot(-wi) / ctx.p().distance_squared(ss.intr.p());
|
|
if ss.pdf.is_infinite() {
|
|
return None;
|
|
}
|
|
|
|
Some(ss)
|
|
}
|
|
|
|
fn pdf(&self, _interaction: &Interaction) -> Float {
|
|
1. / self.area()
|
|
}
|
|
|
|
fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
|
let ray = ctx.spawn_ray(wi);
|
|
if let Some(isect) = self.intersect(&ray, None) {
|
|
let n = isect.intr.n();
|
|
let absdot = Vector3f::from(n).dot(-wi).abs();
|
|
let pdf = (1. / self.area()) / (absdot / ctx.p().distance_squared(isect.intr.p()));
|
|
if pdf.is_infinite() {
|
|
return 0.;
|
|
}
|
|
pdf
|
|
} else {
|
|
0.
|
|
}
|
|
}
|
|
}
|