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>, object_from_render: Arc>, 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 { 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) -> Option { 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) -> 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 { 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 { 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), }) } }