358 lines
14 KiB
Rust
358 lines
14 KiB
Rust
use super::{
|
|
Bounds3f, DirectionCone, Float, Interaction, Normal3f, PI, Point2f, Point3f, Point3fi,
|
|
QuadricIntersection, Ray, ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait,
|
|
SphereShape, SurfaceInteraction, Transform, Vector3f, Vector3fi,
|
|
};
|
|
use crate::core::interaction::InteractionTrait;
|
|
use crate::core::pbrt::{clamp_t, gamma};
|
|
use crate::geometry::{Frame, Sqrt, VectorLike, spherical_direction};
|
|
use crate::utils::interval::Interval;
|
|
use crate::utils::math::{difference_of_products, radians, safe_acos, safe_sqrt, square};
|
|
use crate::utils::sampling::sample_uniform_sphere;
|
|
|
|
use std::mem;
|
|
use std::sync::Arc;
|
|
|
|
impl SphereShape {
|
|
pub fn new(
|
|
render_from_object: Arc<Transform<Float>>,
|
|
object_from_render: Arc<Transform<Float>>,
|
|
reverse_orientation: bool,
|
|
radius: Float,
|
|
z_min: Float,
|
|
z_max: Float,
|
|
phi_max: Float,
|
|
) -> Self {
|
|
let theta_z_min = clamp_t(z_min.min(z_max) / radius, -1., 1.).acos();
|
|
let theta_z_max = clamp_t(z_max.min(z_max) / radius, -1., 1.).acos();
|
|
let phi_max = radians(clamp_t(phi_max, 0., 360.0));
|
|
Self {
|
|
render_from_object: render_from_object.clone(),
|
|
object_from_render: object_from_render.clone(),
|
|
radius,
|
|
z_min: clamp_t(z_min.min(z_max), -radius, radius),
|
|
z_max: clamp_t(z_min.max(z_max), -radius, radius),
|
|
theta_z_max,
|
|
theta_z_min,
|
|
phi_max,
|
|
reverse_orientation,
|
|
transform_swap_handedness: render_from_object.swaps_handedness(),
|
|
}
|
|
}
|
|
|
|
fn basic_intersect(&self, ray: &Ray, t_max: Float) -> Option<QuadricIntersection> {
|
|
let oi: Point3fi = self
|
|
.object_from_render
|
|
.apply_to_interval(&Point3fi::new_from_point(ray.o));
|
|
let di: Vector3fi = self
|
|
.object_from_render
|
|
.apply_to_interval(&Point3fi::new_from_point(Point3f::from(ray.d)))
|
|
.into();
|
|
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 v: Vector3fi = (oi - b / Vector3fi::from((2. * a) * di)).into();
|
|
let length: Interval = v.norm();
|
|
let discrim =
|
|
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);
|
|
}
|
|
|
|
if t0.high > t_max || t1.low < 0. {
|
|
return None;
|
|
}
|
|
let mut t_shape_hit = t0;
|
|
if t_shape_hit.low <= 0. {
|
|
t_shape_hit = t1;
|
|
if t_shape_hit.high > t_max {
|
|
return None;
|
|
}
|
|
}
|
|
|
|
let mut p_hit = Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di);
|
|
if p_hit.x() == 0. && p_hit.y() == 0. {
|
|
p_hit[0] = 1e-5 * self.radius;
|
|
}
|
|
|
|
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_vec =
|
|
Vector3f::from(Point3f::from(oi) + Float::from(t_shape_hit) * Vector3f::from(di));
|
|
p_hit_vec *= self.radius / p_hit.distance(Point3f::zero());
|
|
p_hit = Point3f::from(p_hit_vec);
|
|
|
|
if p_hit.x() == 0. && p_hit.y() == 0. {
|
|
p_hit[0] = 1e-5 * self.radius;
|
|
}
|
|
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
|
|
{
|
|
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 cos_theta = p_hit.z() / self.radius;
|
|
let theta = safe_acos(cos_theta);
|
|
let v = (theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min);
|
|
let z_radius = (square(p_hit.x()) + square(p_hit.y())).sqrt();
|
|
let cos_phi = p_hit.x() / z_radius;
|
|
let sin_phi = p_hit.y() / z_radius;
|
|
let dpdu = Vector3f::new(-self.phi_max * p_hit.y(), self.phi_max * p_hit.x(), 0.);
|
|
let sin_theta = safe_sqrt(1. - square(cos_theta));
|
|
let dpdv = (self.theta_z_max - self.theta_z_min)
|
|
* Vector3f::new(
|
|
p_hit.z() * cos_phi,
|
|
p_hit.z() * sin_phi,
|
|
-self.radius * sin_theta,
|
|
);
|
|
let d2pduu = -self.phi_max * self.phi_max * Vector3f::new(p_hit.x(), p_hit.y(), 0.);
|
|
let d2pduv = (self.theta_z_max - self.theta_z_min)
|
|
* p_hit.z()
|
|
* self.phi_max
|
|
* Vector3f::new(-sin_phi, cos_phi, 0.);
|
|
let d2pdvv = -square(self.theta_z_max - self.theta_z_min) * Vector3f::from(p_hit);
|
|
let e = dpdu.dot(dpdu);
|
|
let f = dpdu.dot(dpdv);
|
|
let g = dpdv.dot(dpdv);
|
|
let n = dpdu.cross(dpdv).normalize();
|
|
let e_min = n.dot(d2pduu);
|
|
let f_min = n.dot(d2pduv);
|
|
let g_min = n.dot(d2pdvv);
|
|
|
|
let efg2 = difference_of_products(e, g, 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(5) * Vector3f::from(p_hit).abs();
|
|
let flip_normal = self.reverse_orientation ^ self.transform_swap_handedness;
|
|
let wo_object = self.object_from_render.apply_to_vector(wo);
|
|
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 SphereShape {
|
|
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 area(&self) -> Float {
|
|
self.phi_max * self.radius * (self.z_max - self.z_min)
|
|
}
|
|
|
|
fn pdf(&self, _interaction: &Interaction) -> Float {
|
|
1. / self.area()
|
|
}
|
|
|
|
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_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float {
|
|
let p_center = self
|
|
.object_from_render
|
|
.apply_to_point(Point3f::new(0., 0., 0.));
|
|
let p_origin = ctx.offset_ray_origin(p_center.into());
|
|
// Return solid angle PDF for point inside sphere
|
|
if p_origin.distance_squared(p_center) <= square(self.radius) {
|
|
let ray = ctx.spawn_ray(wi);
|
|
let isect = self.intersect(&ray, None).expect("Return 0");
|
|
let absdot = isect.intr.n().dot(-Normal3f::from(wi));
|
|
// Compute PDF in solid angle measure from shape intersection point
|
|
let pdf = (1. / self.area()) / (absdot / ctx.p().distance_squared(isect.intr.p()));
|
|
if pdf.is_infinite() {
|
|
return 0.;
|
|
}
|
|
return pdf;
|
|
}
|
|
let sin2_theta_max = self.radius * self.radius / ctx.p().distance_squared(p_center);
|
|
let cos_theta_max = safe_sqrt(1. - sin2_theta_max);
|
|
let mut one_minus_cos_theta_max = 1. - cos_theta_max;
|
|
// Compute more accurate cos theta max for small solid angle
|
|
if sin2_theta_max < 0.00068523 {
|
|
one_minus_cos_theta_max = sin2_theta_max / 2.;
|
|
}
|
|
|
|
1. / (2. * PI * one_minus_cos_theta_max)
|
|
}
|
|
|
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
|
let p_obj = Point3f::new(0., 0., 0.) + self.radius * sample_uniform_sphere(u);
|
|
let mut p_obj_vec = Vector3f::from(p_obj);
|
|
p_obj_vec *= self.radius / p_obj.distance(Point3f::zero());
|
|
let p_obj_error = gamma(5) * p_obj_vec.abs();
|
|
let n_obj = Normal3f::from(p_obj_vec);
|
|
let mut n = self.render_from_object.apply_to_normal(n_obj).normalize();
|
|
if self.reverse_orientation {
|
|
n *= -1.;
|
|
}
|
|
let theta = safe_acos(p_obj_vec.z() / self.radius);
|
|
let mut phi = p_obj_vec.y().atan2(p_obj_vec.x());
|
|
if phi < 0. {
|
|
phi += 2. * PI;
|
|
}
|
|
let uv = Point2f::new(
|
|
phi / self.phi_max,
|
|
(theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min),
|
|
);
|
|
let pi = self
|
|
.render_from_object
|
|
.apply_to_interval(&Point3fi::new_with_error(
|
|
Point3f::from(p_obj_vec),
|
|
p_obj_error,
|
|
));
|
|
let si = SurfaceInteraction::new_simple(pi, n, uv);
|
|
Some(ShapeSample {
|
|
intr: Arc::new(si),
|
|
pdf: 1. / self.area(),
|
|
})
|
|
}
|
|
|
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
|
let p_center = self.render_from_object.apply_to_point(Point3f::zero());
|
|
let p_origin = ctx.offset_ray_origin_from_point(p_center);
|
|
if p_origin.distance_squared(p_center) <= square(self.radius) {
|
|
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;
|
|
}
|
|
return Some(ss);
|
|
}
|
|
let sin_theta_max = self.radius / ctx.p().distance(p_center);
|
|
let sin2_theta_max = square(sin_theta_max);
|
|
let cos_theta_max = safe_sqrt(1. - sin2_theta_max);
|
|
let mut one_minus_cos_theta_max = 1. - cos_theta_max;
|
|
let mut cos_theta = (cos_theta_max - 1.) * u[0] + 1.;
|
|
let mut sin2_theta = 1. - square(cos_theta);
|
|
|
|
// Compute more accurate cos theta max for small solid angle
|
|
if sin2_theta_max < 0.00068523 {
|
|
sin2_theta = sin2_theta_max * u[0];
|
|
cos_theta = (1. - sin2_theta).sqrt();
|
|
one_minus_cos_theta_max = sin2_theta_max / 2.;
|
|
}
|
|
// Compute angle alpha from center of sphere to sampled point on surface
|
|
let cos_alpha = sin2_theta / sin_theta_max
|
|
+ cos_theta * safe_sqrt(1. - sin2_theta / square(sin_theta_max));
|
|
let sin_alpha = safe_sqrt(1. - square(cos_alpha));
|
|
let phi = u[1] * 2. * PI;
|
|
let w = spherical_direction(sin_alpha, cos_alpha, phi);
|
|
let sampling_frame = Frame::from_z((p_center - ctx.p()).normalize());
|
|
let mut n: Normal3f = sampling_frame.from_local(-w).into();
|
|
let p = p_center + self.radius * Vector3f::from(n);
|
|
if self.reverse_orientation {
|
|
n *= -1.;
|
|
}
|
|
let p_error = gamma(5) * Vector3f::from(p).abs();
|
|
// Compute (u, v) coordinates for sampled point on sphere
|
|
let p_obj = self.object_from_render.apply_to_point(Point3f::from(p));
|
|
let theta = safe_acos(p_obj.z() / self.radius);
|
|
let mut sphere_phi = p_obj.y().atan2(p_obj.x());
|
|
if sphere_phi < 0. {
|
|
sphere_phi += 2. * PI;
|
|
}
|
|
let uv = Point2f::new(
|
|
sphere_phi / self.phi_max,
|
|
(theta - self.theta_z_min) / (self.theta_z_max - self.theta_z_min),
|
|
);
|
|
let pi = Point3fi::new_with_error(p_obj, p_error);
|
|
let si = SurfaceInteraction::new_simple(pi, n, uv);
|
|
Some(ShapeSample {
|
|
intr: Arc::new(si),
|
|
pdf: 1. / (2. * PI * one_minus_cos_theta_max),
|
|
})
|
|
}
|
|
}
|