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::interaction::{ Interaction, InteractionBase, InteractionTrait, SimpleInteraction, SurfaceInteraction, }; use crate::core::shape::{ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait}; use crate::shapes::mesh::TriangleMesh; use crate::utils::math::{difference_of_products, square}; use crate::utils::sampling::{ bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle, sample_uniform_triangle, }; use crate::{gamma, Float, GVec, Ptr}; #[repr(C)] #[derive(Debug, Clone, Copy)] pub struct TriangleIntersection { b0: Float, b1: Float, b2: Float, t: Float, } impl TriangleIntersection { pub fn new(b0: Float, b1: Float, b2: Float, t: Float) -> Self { Self { b0, b1, b2, t } } } #[repr(C)] #[derive(Clone, Copy, Debug)] pub struct TriangleShape { pub mesh: Ptr, pub tri_index: i32, } impl TriangleShape { pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4; pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22; fn mesh(&self) -> &TriangleMesh { self.mesh.get().unwrap() } fn get_vertex_indices(&self) -> [usize; 3] { let mesh = self.mesh(); let base = (self.tri_index as usize) * 3; [ mesh.vertex_indices[base] as usize, mesh.vertex_indices[base + 1] as usize, mesh.vertex_indices[base + 2] as usize, ] } fn get_points(&self) -> [Point3f; 3] { let mesh = self.mesh(); let [v0, v1, v2] = self.get_vertex_indices(); [mesh.p[v0], mesh.p[v1], mesh.p[v2]] } fn get_shading_normals(&self) -> Option<[Normal3f; 3]> { let mesh = self.mesh(); if mesh.n.is_empty() { return None; } let [v0, v1, v2] = self.get_vertex_indices(); Some([mesh.n[v0], mesh.n[v1], mesh.n[v2]]) } fn get_tangents(&self) -> Option<[Vector3f; 3]> { let mesh = self.mesh(); if mesh.s.is_empty() { return None; } let [v0, v1, v2] = self.get_vertex_indices(); Some([mesh.s[v0], mesh.s[v1], mesh.s[v2]]) } fn get_uvs(&self) -> Option<[Point2f; 3]> { let mesh = self.mesh(); if mesh.uv.is_empty() { return None; } let [v0, v1, v2] = self.get_vertex_indices(); Some([mesh.uv[v0], mesh.uv[v1], mesh.uv[v2]]) } pub fn new(mesh: Ptr, tri_index: i32) -> Self { Self { mesh, tri_index } } pub fn get_mesh(&self) -> Ptr { self.mesh } pub fn solid_angle(&self, p: Point3f) -> Float { let [p0, p1, p2] = self.get_points(); spherical_triangle_area( (p0 - p).normalize(), (p1 - p).normalize(), (p2 - p).normalize(), ) } fn intersect_triangle( &self, ray: &Ray, t_max: Float, p0: Point3f, p1: Point3f, p2: Point3f, ) -> Option { if (p2 - p0).cross(p1 - p0).norm_squared() == 0. { return None; } // Transform triangle vertices to ray coordinate space // Translate vertices based on ray origin let mut p0t = p0 - Vector3f::from(ray.o); let mut p1t = p1 - Vector3f::from(ray.o); let mut p2t = p2 - Vector3f::from(ray.o); // Permute components of triangle vertices and ray direction let kz = ray.d.abs().max_component_index(); let mut kx = kz + 1; if kx == 3 { kx = 0; } let mut ky = kx + 1; if ky == 3 { ky = 0; } let d = ray.d.permute([kx, ky, kz]); p0t = p0t.permute([kx, ky, kz]); p1t = p1t.permute([kx, ky, kz]); p2t = p2t.permute([kx, ky, kz]); // Apply shear transformation to translated vertex positions let sx = -d.x() / d.z(); let sy = -d.y() / d.z(); let sz = 1. / d.z(); p0t[0] += sx * p0t[2]; p0t[1] += sy * p0t[2]; p1t[0] += sx * p1t[2]; p1t[1] += sy * p1t[2]; p2t[0] += sx * p2t[2]; p2t[1] += sy * p2t[2]; // Compute edge function coefficients _e0_, _e1_, and _e2_ let e0 = difference_of_products(p1t.x(), p2t.y(), p1t.y(), p2t.x()); let e1 = difference_of_products(p2t.x(), p0t.y(), p2t.y(), p0t.x()); let e2 = difference_of_products(p0t.x(), p1t.y(), p0t.y(), p1t.x()); // Fall back to double-precision test at triangle edges // if sizeof(Float) == sizeof(float) && (e0 == 0.0f || e1 == 0.0f || e2 == 0.0f)) { // double p2txp1ty = (double)p2t.x * (double)p1t.y; // double p2typ1tx = (double)p2t.y * (double)p1t.x; // e0 = (float)(p2typ1tx - p2txp1ty); // double p0txp2ty = (double)p0t.x * (double)p2t.y; // double p0typ2tx = (double)p0t.y * (double)p2t.x; // e1 = (float)(p0typ2tx - p0txp2ty); // double p1txp0ty = (double)p1t.x * (double)p0t.y; // double p1typ0tx = (double)p1t.y * (double)p0t.x; // e2 = (float)(p1typ0tx - p1txp0ty); // } // Perform triangle edge and determinant tests if (e0 < 0. || e1 < 0. || e2 < 0.) && (e0 > 0. || e1 > 0. || e2 > 0.) { return None; } let det = e0 + e1 + e2; if det == 0. { return None; } // Compute scaled hit distance to triangle and test against ray $t$ range p0t[2] *= sz; p1t[2] *= sz; p2t[2] *= sz; let t_scaled = e0 * p0t.z() + e1 * p1t.z() + e2 * p2t.z(); if det < 0. && (t_scaled >= 0. || t_scaled < t_max * det) { return None; } else if det > 0. && (t_scaled <= 0. || t_scaled > t_max * det) { return None; } // Compute barycentric coordinates and $t$ value for triangle intersection let inv_det = 1. / det; let b0 = e0 * inv_det; let b1 = e1 * inv_det; let b2 = e2 * inv_det; let t = t_scaled * inv_det; debug_assert!(t.is_finite()); // 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()) .abs() .max_component_value(); let deltaZ = gamma(3) * maxZt; // Compute $\delta_x$ and $\delta_y$ terms for triangle $t$ error bounds let maxXt = Vector3f::new(p0t.x(), p1t.x(), p2t.x()) .abs() .max_component_value(); let maxYt = Vector3f::new(p0t.y(), p1t.y(), p2t.y()) .abs() .max_component_value(); let deltaX = gamma(5) * (maxXt + maxZt); let deltaY = gamma(5) * (maxYt + maxZt); // Compute $\delta_e$ term for triangle $t$ error bounds let deltaE = 2. * (gamma(2) * maxXt * maxYt + deltaY * maxXt + deltaX * maxYt); // 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 { return None; } // Return _TriangleIntersection_ for intersection return Some(TriangleIntersection { b0, b1, b2, t }); } fn interaction_from_intersection( &self, ti: TriangleIntersection, time: Float, wo: Vector3f, ) -> SurfaceInteraction { let [p0, p1, p2] = self.get_points(); let uv = self.get_uvs().unwrap_or([ Point2f::new(0.0, 0.0), Point2f::new(1.0, 0.0), Point2f::new(1.0, 1.0), ]); let duv02 = uv[0] - uv[2]; let duv12 = uv[1] - uv[2]; let dp02 = p0 - p2; let dp12 = p1 - p2; let determinant = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]); let degenerate = determinant.abs() < 1e-9; let (mut dpdu, mut dpdv) = if !degenerate { let invdet = 1. / determinant; let ret0 = difference_of_products(duv12[1], dp02, duv02[1], dp12) * invdet; let ret1 = difference_of_products(duv02[0], dp12, duv12[0], dp02) * invdet; (ret0, ret1) } else { (Vector3f::zero(), Vector3f::zero()) }; if degenerate || dpdu.cross(dpdv).norm_squared() == 0. { let mut ng = (p2 - p0).cross(p1 - p0); if ng.norm_squared() == 0. { let v1 = p2 - p0; let v2 = p1 - p0; ng = v1.cast::().cross(v2.cast::()).cast::(); assert!(ng.norm_squared() != 0.); } (dpdu, dpdv) = ng.normalize().coordinate_system(); } let p0_vec = Vector3f::from(p0); let p1_vec = Vector3f::from(p1); let p2_vec = Vector3f::from(p2); let p_hit = Point3f::from(ti.b0 * p0_vec + ti.b1 * p1_vec + ti.b2 * p2_vec); let uv_hit = Point2f::from( ti.b0 * Vector2f::from(uv[0]) + ti.b1 * Vector2f::from(uv[1]) + ti.b2 * Vector2f::from(uv[2]), ); let p_abs_sum = (ti.b0 * p0_vec).abs() + (ti.b1 * p1_vec).abs() + (ti.b2 * p2_vec).abs(); let p_error = gamma(7) * p_abs_sum; let mut ng = Normal3f::from(dp02.cross(dp12).normalize()); let flip_normal = self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness; if flip_normal { ng = -ng; } let mut isect = SurfaceInteraction::new( Point3fi::new_with_error(p_hit, p_error), uv_hit, wo, dpdu, dpdv, Normal3f::zero(), Normal3f::zero(), time, flip_normal, ); isect.face_index = if !self.mesh.face_indices.is_empty() { unsafe { *self.mesh.face_indices.as_ptr().add(self.tri_index as usize) } } else { 0 }; isect.common.n = ng; isect.shading.n = ng; if !self.mesh.n.is_empty() || !self.mesh.s.is_empty() { self.compute_shading_geometry(&mut isect, &ti, uv, dpdu, determinant, degenerate); } isect } fn compute_shading_geometry( &self, isect: &mut SurfaceInteraction, ti: &TriangleIntersection, uv: [Point2f; 3], dpdu_geom: Vector3f, determinant: Float, degenerate_uv: bool, ) { let ns = if let Some(normals) = self.get_shading_normals() { let n = ti.b0 * normals[0] + ti.b1 * normals[1] + ti.b2 * normals[2]; if n.norm_squared() > 0.0 { n.normalize() } else { isect.n() } } else { isect.n() }; let mut ss = if let Some(tangents) = self.get_tangents() { let s = ti.b0 * tangents[0] + ti.b1 * tangents[1] + ti.b2 * tangents[2]; if s.norm_squared() > 0.0 { s.normalize() } else { dpdu_geom } } else { dpdu_geom }; let mut ts = ns.cross(ss.into()); if ts.norm_squared() > 0.0 { ss = ts.cross(ns.into()).into(); } else { let (s, t) = ns.coordinate_system(); ss = s.into(); ts = t.into(); } let (dndu, dndv) = if let Some(normals) = self.get_shading_normals() { if degenerate_uv { let dn = (normals[2] - normals[0]).cross(normals[1] - normals[0]); if dn.norm_squared() == 0.0 { (Normal3f::zero(), Normal3f::zero()) } else { let (dnu, dnv) = dn.coordinate_system(); (Normal3f::from(dnu), Normal3f::from(dnv)) } } else { let dn1 = normals[0] - normals[2]; let dn2 = normals[1] - normals[2]; let duv02 = uv[0] - uv[2]; let duv12 = uv[1] - uv[2]; let inv_det = 1.0 / determinant; ( difference_of_products(duv12[1], dn1, duv02[1], dn2) * inv_det, difference_of_products(duv02[0], dn2, duv12[0], dn1) * inv_det, ) } } else { (Normal3f::zero(), Normal3f::zero()) }; isect.set_shading_geom(ns, ss, ts.into(), dndu, dndv, true); } } impl ShapeTrait for TriangleShape { fn bounds(&self) -> Bounds3f { let [p0, p1, p2] = self.get_points(); Bounds3f::from_points(p0, p1).union_point(p2) } fn normal_bounds(&self) -> DirectionCone { let [p0, p1, p2] = self.get_points(); let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into(); if let Some(normals) = self.get_shading_normals() { let [n0, n1, n2] = normals; let ns = n0 + n1 + n2; n = n.face_forward(ns); } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness { n = -n; } DirectionCone::new_from_vector(n.into()) } fn area(&self) -> Float { let [p0, p1, p2] = self.get_points(); 0.5 * (p1 - p0).cross(p2 - p0).norm() } fn sample(&self, u: Point2f) -> Option { let [p0, p1, p2] = self.get_points(); let b = sample_uniform_triangle(u); let p = p0 + b[1] * (p1 - p0) + b[2] * (p2 - p0); let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into(); if let Some(normals) = self.get_shading_normals() { let [n0, n1, n2] = normals; let ns = b[0] * n0 + b[1] * n1 + b[2] * n2; // b[2] is (1 - b0 - b1) n = n.face_forward(ns); } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness { n = -n; } let uv_sample = if let Some(uvs) = self.get_uvs() { let [uv0, uv1, uv2] = uvs; uv0 + b[1] * (uv1 - uv0) + b[2] * (uv2 - uv0) } else { let v = b[0] * Vector2f::new(0.0, 0.0) + b[1] * Vector2f::new(1.0, 0.0) + b[2] * Vector2f::new(1.0, 1.0); Point2f::from(v) }; let p0_v = Vector3f::from(p0); let p1_v = Vector3f::from(p1); let p2_v = Vector3f::from(p2); let p_abs_sum = (b[0] * p0_v).abs() + (b[1] * p1_v).abs() + (b[2] * p2_v).abs(); let p_error = Vector3f::from(p_abs_sum) * gamma(6); let intr_base = InteractionBase::new_surface_geom( Point3fi::new_with_error(p, p_error), n, uv_sample, Vector3f::default(), 0., ); Some(ShapeSample { intr: Interaction::Simple(SimpleInteraction::new(intr_base)), pdf: 1.0 / self.area(), }) } fn sample_from_context(&self, ctx: &ShapeSampleContext, mut u: Point2f) -> Option { let [p0, p1, p2] = self.get_points(); let solid_angle = self.solid_angle(ctx.p()); if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA || solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA { let mut ss = self.sample(u)?; ss.intr.get_common_mut().time = ctx.time; let mut wi: Normal3f = (ss.intr.p() - ctx.p()).into(); if wi.norm_squared() == 0. { return None; } wi = wi.normalize(); ss.pdf /= ss.intr.n().abs_dot(-wi) / ctx.p().distance_squared(ss.intr.p()); if ss.pdf.is_infinite() { return None; } return Some(ss); } let mut pdf = 1.; if ctx.ns != Normal3f::zero() { let rp = ctx.p(); let wi: [Vector3f; 3] = [ (p0 - rp).normalize(), (p1 - rp).normalize(), (p2 - rp).normalize(), ]; let w: [Float; 4] = [ ctx.ns.abs_dot(wi[1].into()).max(0.01), ctx.ns.abs_dot(wi[1].into()).max(0.01), ctx.ns.abs_dot(wi[0].into()).max(0.01), ctx.ns.abs_dot(wi[2].into()).max(0.01), ]; u = sample_bilinear(u, &w); pdf = bilinear_pdf(u, &w); } let (b, tri_pdf) = sample_spherical_triangle(&[p0, p1, p2], ctx.p(), u)?; if tri_pdf == 0. { return None; } let p0_v = Vector3f::from(p0); let p1_v = Vector3f::from(p1); let p2_v = Vector3f::from(p2); let p_abs_sum = (b[0] * p0_v).abs() + (b[1] * p1_v).abs() + (b[2] * p2_v).abs(); let mut n: Normal3f = (p1 - p0).cross(p2 - p0).normalize().into(); if let Some(normals) = self.get_shading_normals() { let [n0, n1, n2] = normals; let ns = b[0] * n0 + b[1] * n1 + (1. - b[0] - b[1]) * n2; n = n.face_forward(ns); } else if self.mesh.reverse_orientation ^ self.mesh.transform_swaps_handedness { n = -n; } let uv_sample = if let Some(uvs) = self.get_uvs() { let [uv0, uv1, uv2] = uvs; uv0 + b[1] * (uv1 - uv0) + b[2] * (uv2 - uv0) } else { let v = b[0] * Vector2f::new(0.0, 0.0) + b[1] * Vector2f::new(1.0, 0.0) + b[2] * Vector2f::new(1.0, 1.0); Point2f::from(v) }; let p = p0 + b[1] * (p1 - p0) + b[2] * (p2 - p0); let p_error = Vector3f::from(p_abs_sum) * gamma(6); let intr_base = InteractionBase::new_surface_geom( Point3fi::new_with_error(p, p_error), n, uv_sample, Vector3f::default(), 0., ); Some(ShapeSample { intr: Interaction::Simple(SimpleInteraction::new(intr_base)), pdf, }) } fn intersect(&self, ray: &Ray, t_max: Option) -> Option { let [p0, p1, p2] = self.get_points(); 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)) } fn intersect_p(&self, ray: &Ray, t_max: Option) -> bool { let [p0, p1, p2] = self.get_points(); let tri_isect = self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY), p0, p1, p2); tri_isect.is_some() } fn pdf(&self, _interaction: &Interaction) -> Float { 1. / self.area() } fn pdf_from_context(&self, ctx: &ShapeSampleContext, wi: Vector3f) -> Float { let solid_angle = self.solid_angle(ctx.p()); if solid_angle < Self::MIN_SPHERICAL_SAMPLE_AREA || solid_angle > Self::MAX_SPHERICAL_SAMPLE_AREA { let ray = ctx.spawn_ray(wi); let Some(isect) = self.intersect(&ray, None) else { return 0.; }; let pdf = (1. / self.area()) / (isect.intr.n().abs_dot(-Normal3f::from(wi)) / ctx.p().distance_squared(isect.intr.p())); if pdf.is_infinite() { return 0.; } return pdf; } let mut pdf = 1. / solid_angle; if ctx.ns != Normal3f::zero() { let [p0, p1, p2] = self.get_points(); let u = invert_spherical_triangle_sample(&[p0, p1, p2], ctx.p(), wi) .expect("Could not calculate inverse sample"); let rp = ctx.p(); let wi = [ (p0 - rp).normalize(), (p1 - rp).normalize(), (p2 - rp).normalize(), ]; let w: [Float; 4] = [ ctx.ns.abs_dot(wi[1].into()).max(0.01), ctx.ns.abs_dot(wi[1].into()).max(0.01), ctx.ns.abs_dot(wi[0].into()).max(0.01), ctx.ns.abs_dot(wi[2].into()).max(0.01), ]; pdf *= bilinear_pdf(u, &w); } pdf } }