pbrt/shared/src/shapes/cylinder.rs

270 lines
9.5 KiB
Rust

use super::{
Bounds3f, CylinderShape, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f,
Point3fi, QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext,
ShapeTrait, SurfaceInteraction, Transform, Vector3f, Vector3fi,
};
use crate::core::geometry::{Sqrt, Tuple, VectorLike};
use crate::core::interaction::InteractionTrait;
use crate::core::pbrt::gamma;
use crate::utils::interval::Interval;
use crate::utils::math::{difference_of_products, lerp, square};
use std::mem;
use std::sync::Arc;
impl CylinderShape {
pub fn new(
render_from_object: Arc<Transform>,
object_from_render: Arc<Transform>,
reverse_orientation: bool,
radius: Float,
z_min: Float,
z_max: Float,
phi_max: Float,
) -> Self {
Self {
radius,
z_min,
z_max,
phi_max,
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> {
// Transform Ray origin and direction to object space
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));
// Solve quadratic equation to find cylinder t0 and t1 values>>
let a: Interval = square(di.x()) + square(di.y()) + square(di.z());
let b: Interval = 2. * (di.x() * oi.x() + di.y() * oi.y() + di.z() * oi.z());
let c: Interval =
square(oi.x()) + square(oi.y()) + square(oi.z()) - square(Interval::new(self.radius));
let f = b / (2. * a);
let vx: Interval = oi.x() - f * di.x();
let vy: Interval = oi.y() - f * di.y();
let length: Interval = (square(vx) + square(vy)).sqrt();
let discrim: Interval =
4. * a * (Interval::new(self.radius) * length) * (Interval::new(self.radius) - length);
if discrim.low < 0. {
return None;
}
let root_discrim = discrim.sqrt();
let q = if Float::from(b) < 0. {
-0.5 * (b - root_discrim)
} else {
-0.5 * (b + root_discrim)
};
let mut t0 = q / a;
let mut t1 = c / q;
if t0.low > t1.low {
mem::swap(&mut t0, &mut t1);
}
// Check quadric shape t0 and t1 for nearest intersection
if t0.high > t_max || t1.low < 0. {
return None;
}
let mut t_shape_hit: Interval = t0;
if t_shape_hit.low <= 0. {
t_shape_hit = t1;
if t_shape_hit.high > t_max {
return None;
}
}
// Compute cylinder hit point and phi
let mut p_hit = Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di);
let hit_rad = (square(p_hit.x()) + square(p_hit.y())).sqrt();
p_hit[0] *= self.radius / hit_rad;
p_hit[1] *= self.radius / hit_rad;
let mut phi = p_hit.y().atan2(p_hit.x());
if phi < 0. {
phi += 2. * PI;
}
if self.z_min > -self.radius && p_hit.z() < self.z_min
|| self.z_max < self.radius && p_hit.z() > self.z_max
|| phi > self.phi_max
{
if t_shape_hit == t1 {
return None;
}
if t1.high > t_max {
return None;
}
t_shape_hit = t1;
let mut p_hit =
Vector3f::from(Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di));
let hit_rad = (square(p_hit.x()) + square(p_hit.y())).sqrt();
p_hit[0] *= self.radius / hit_rad;
p_hit[1] *= self.radius / hit_rad;
phi = p_hit.y().atan2(p_hit.x());
if phi < 0. {
phi += 2. * PI;
}
if p_hit.z() < self.z_min || p_hit.z() > self.z_max || phi > self.phi_max {
return None;
}
}
Some(QuadricIntersection::new(t_shape_hit.into(), p_hit, phi))
}
fn interaction_from_intersection(
&self,
isect: QuadricIntersection,
wo: Vector3f,
time: Float,
) -> SurfaceInteraction {
let p_hit = isect.p_obj;
let phi = isect.phi;
let u = phi / self.phi_max;
let v = (p_hit.z() - self.z_min) / (self.z_max - self.z_min);
let dpdu = Vector3f::new(-self.phi_max * p_hit.y(), self.phi_max * p_hit.x(), 0.);
let dpdv = Vector3f::new(0., 0., self.z_max - self.z_min);
let d2pduu = -self.phi_max * self.phi_max * Vector3f::new(p_hit.x(), p_hit.y(), 0.);
let d2pduv = Vector3f::zero();
let d2pdvv = Vector3f::zero();
let e = dpdu.dot(dpdu);
let f = dpdu.dot(dpdv);
let g = dpdv.dot(dpdv);
let n: Vector3f = dpdu.cross(dpdv).normalize();
let e_min = n.dot(d2pduu);
let f_min = n.dot(d2pduv);
let g_min = n.dot(d2pdvv);
// Compute dn/du and dn/dv from fundamental form coefficients
let efg2 = difference_of_products(e, f, f, f);
let inv_efg2 = if efg2 == 0. { 0. } else { 1. / efg2 };
let dndu = Normal3f::from(
(f_min * f - e_min * g) * inv_efg2 * dpdu + (e_min * f - f_min * e) * inv_efg2 * dpdv,
);
let dndv = Normal3f::from(
(g_min * f - f_min * g) * inv_efg2 * dpdu + (f_min * f - g_min * e) * inv_efg2 * dpdv,
);
let p_error = gamma(3) * Vector3f::new(p_hit.x(), p_hit.y(), 0.).abs();
let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
let wo_object = self.object_from_render.apply_to_vector(wo);
// (*renderFromObject)
SurfaceInteraction::new(
Point3fi::new_with_error(p_hit, p_error),
Point2f::new(u, v),
wo_object,
dpdu,
dpdv,
dndu,
dndv,
time,
flip_normal,
)
}
}
impl ShapeTrait for CylinderShape {
fn area(&self) -> Float {
(self.z_max - self.z_min) * self.radius * self.phi_max
}
fn bounds(&self) -> Bounds3f {
self.render_from_object
.apply_to_bounds(Bounds3f::from_points(
Point3f::new(-self.radius, -self.radius, self.z_min),
Point3f::new(self.radius, self.radius, self.z_max),
))
}
fn normal_bounds(&self) -> DirectionCone {
DirectionCone::entire_sphere()
}
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 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 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.
}
}
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
let z = lerp(u[0], self.z_min, self.z_max);
let phi = u[1] * self.phi_max;
let mut p_obj = Point3f::new(self.radius * phi.cos(), self.radius * phi.sin(), z);
let hit_rad = (square(p_obj.x()) + square(p_obj.y())).sqrt();
p_obj[0] *= self.radius / hit_rad;
p_obj[1] *= self.radius / hit_rad;
let p_obj_error = gamma(3) * Vector3f::new(p_obj.x(), p_obj.y(), 0.).abs();
let pi = self
.render_from_object
.apply_to_interval(&Point3fi::new_with_error(p_obj, p_obj_error));
let mut n = self
.render_from_object
.apply_to_normal(Normal3f::new(p_obj.x(), p_obj.y(), 0.))
.normalize();
if self.reverse_orientation {
n *= -1.;
}
let uv = Point2f::new(
phi / self.phi_max,
(p_obj.z() - self.z_min) / (self.z_max - self.z_min),
);
Some(ShapeSample {
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv)),
pdf: 1. / self.area(),
})
}
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
let mut ss = self.sample(u)?;
let intr = Arc::make_mut(&mut ss.intr);
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()).dot(-wi).abs() / ctx.p().distance_squared(ss.intr.p());
if ss.pdf.is_infinite() {
return None;
}
Some(ss)
}
}