512 lines
18 KiB
Rust
512 lines
18 KiB
Rust
use super::{
|
|
Bounds3f, DirectionCone, Float, Interaction, Normal3f, Point2f, Point3f, Point3fi, Ray,
|
|
ShapeIntersection, ShapeSample, ShapeSampleContext, ShapeTrait, SurfaceInteraction,
|
|
TriangleIntersection, TriangleShape, Vector2f, Vector3f,
|
|
};
|
|
use crate::core::interaction::InteractionTrait;
|
|
use crate::core::pbrt::gamma;
|
|
use crate::geometry::{Sqrt, Tuple, VectorLike, spherical_triangle_area};
|
|
use crate::utils::math::{difference_of_products, square};
|
|
use crate::utils::mesh::TriangleMesh;
|
|
use crate::utils::sampling::{
|
|
bilinear_pdf, invert_spherical_triangle_sample, sample_bilinear, sample_spherical_triangle,
|
|
sample_uniform_triangle,
|
|
};
|
|
use std::mem;
|
|
use std::sync::{Arc, OnceLock};
|
|
|
|
pub static TRIANGLE_MESHES: OnceLock<Vec<Arc<TriangleMesh>>> = OnceLock::new();
|
|
|
|
#[derive(Clone, Copy)]
|
|
struct TriangleData {
|
|
vertices: [Point3f; 3],
|
|
uvs: [Point2f; 3],
|
|
normals: Option<[Normal3f; 3]>,
|
|
area: Float,
|
|
normal: Normal3f,
|
|
reverse_orientation: bool,
|
|
transform_swaps_handedness: bool,
|
|
}
|
|
|
|
impl TriangleShape {
|
|
pub const MIN_SPHERICAL_SAMPLE_AREA: Float = 3e-4;
|
|
pub const MAX_SPHERICAL_SAMPLE_AREA: Float = 6.22;
|
|
|
|
fn mesh(&self) -> &Arc<TriangleMesh> {
|
|
let meshes = TRIANGLE_MESHES
|
|
.get()
|
|
.expect("Mesh has not been initialized");
|
|
&meshes[self.mesh_ind]
|
|
}
|
|
|
|
fn get_data(&self) -> TriangleData {
|
|
let mesh = self.mesh();
|
|
let start = 3 * self.tri_index;
|
|
let indices = &mesh.vertex_indices[start..start + 3];
|
|
let vertices = [mesh.p[indices[0]], mesh.p[indices[1]], mesh.p[indices[2]]];
|
|
let uvs = mesh.uv.as_ref().map_or(
|
|
[
|
|
Point2f::zero(),
|
|
Point2f::new(1.0, 0.0),
|
|
Point2f::new(1.0, 1.0),
|
|
],
|
|
|uv| [uv[indices[0]], uv[indices[1]], uv[indices[2]]],
|
|
);
|
|
let normals = mesh
|
|
.n
|
|
.as_ref()
|
|
.map(|n| [n[indices[0]], n[indices[1]], n[indices[2]]]);
|
|
let dp1 = vertices[1] - vertices[0];
|
|
let dp2 = vertices[2] - vertices[0];
|
|
let normal = Normal3f::from(dp1.cross(dp2).normalize());
|
|
let area = 0.5 * dp1.cross(dp2).norm();
|
|
|
|
TriangleData {
|
|
vertices,
|
|
uvs,
|
|
normals,
|
|
area,
|
|
normal,
|
|
reverse_orientation: mesh.reverse_orientation,
|
|
transform_swaps_handedness: mesh.transform_swaps_handedness,
|
|
}
|
|
}
|
|
|
|
fn solid_angle(&self, p: Point3f) -> Float {
|
|
let data = self.get_data();
|
|
let [p0, p1, p2] = data.vertices;
|
|
spherical_triangle_area(
|
|
(p0 - p).normalize(),
|
|
(p1 - p).normalize(),
|
|
(p2 - p).normalize(),
|
|
)
|
|
}
|
|
|
|
fn intersect_triangle(&self, ray: &Ray, t_max: Float) -> Option<TriangleIntersection> {
|
|
let data = self.get_data();
|
|
let [p0, p1, p2] = data.vertices;
|
|
if (p2 - p0).cross(p1 - p0).norm_squared() == 0. {
|
|
return None;
|
|
}
|
|
let mut p0t = p0 - Vector3f::from(ray.o);
|
|
let mut p1t = p1 - Vector3f::from(ray.o);
|
|
let mut p2t = p2 - Vector3f::from(ray.o);
|
|
|
|
let kz = ray.d.abs().max_component_index();
|
|
let kx = if kz == 3 { 0 } else { kz + 1 };
|
|
let ky = if kz == 3 { 0 } else { kx + 1 };
|
|
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.z();
|
|
p0t[1] += sy * p0t.z();
|
|
p1t[0] += sx * p1t.z();
|
|
p1t[1] += sy * p1t.z();
|
|
p2t[0] += sx * p2t.z();
|
|
p2t[0] += sy * p2t.z();
|
|
|
|
// Compute edge function coefficients e0, e1, and e2
|
|
let mut e0 = difference_of_products(p1t.x(), p2t.y(), p1t.y(), p2t.x());
|
|
let mut e1 = difference_of_products(p2t.x(), p0t.y(), p2t.y(), p0t.x());
|
|
let mut e2 = difference_of_products(p0t.x(), p1t.y(), p0t.y(), p1t.x());
|
|
|
|
// if mem::size_of::<Float>() == mem::size_of::<f32>() && (e0 == 0.0 || e1 == 0.0 || e2 == 0.0)
|
|
if e0 == 0.0 || e1 == 0.0 || e2 == 0.0 {
|
|
let [p0t64, p1t64, p2t64] = [p0t.cast::<f64>(), p1t.cast::<f64>(), p2t.cast::<f64>()];
|
|
|
|
e0 = (p2t64.y() * p1t64.x() - p2t64.x() * p1t64.y()) as Float;
|
|
e1 = (p0t64.y() * p2t64.x() - p0t64.x() * p2t64.y()) as Float;
|
|
e2 = (p1t64.y() * p0t64.x() - p1t64.x() * p0t64.y()) as Float;
|
|
}
|
|
|
|
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
|
|
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)
|
|
|| (det > 0. && (t_scaled <= 0. || t_scaled > t_max * det))
|
|
{
|
|
return None;
|
|
}
|
|
|
|
// Compute barycentric coordinates and 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;
|
|
|
|
// Ensure that computed triangle is conservatively greater than zero
|
|
let max_z_t = Vector3f::new(p0t.z(), p1t.z(), p2t.z())
|
|
.abs()
|
|
.max_component_value();
|
|
let delta_z = gamma(3) * max_z_t;
|
|
|
|
let max_x_t = Vector3f::new(p0t.x(), p1t.x(), p2t.x())
|
|
.abs()
|
|
.max_component_value();
|
|
let max_y_t = Vector3f::new(p0t.y(), p1t.y(), p2t.y())
|
|
.abs()
|
|
.max_component_value();
|
|
let delta_x = gamma(5) * (max_x_t + max_z_t);
|
|
let delta_y = gamma(5) * (max_y_t + max_z_t);
|
|
|
|
let delta_e = 2. * (gamma(2) * max_x_t * max_y_t + delta_y * max_x_t + delta_x * max_y_t);
|
|
let max_e = Vector3f::new(e0, e1, e2).abs().max_component_value();
|
|
let delta_t =
|
|
3. * (gamma(3) * max_e * max_z_t + delta_e * max_z_t + delta_z * max_e) * inv_det.abs();
|
|
|
|
if t <= delta_t {
|
|
return None;
|
|
}
|
|
|
|
Some(TriangleIntersection::new(b0, b1, b2, t))
|
|
}
|
|
|
|
fn interaction_from_intersection(
|
|
&self,
|
|
ti: TriangleIntersection,
|
|
time: Float,
|
|
wo: Vector3f,
|
|
) -> SurfaceInteraction {
|
|
let data = self.get_data();
|
|
let [p0, p1, p2] = data.vertices;
|
|
let [uv0, uv1, uv2] = data.uvs;
|
|
// Compute triangle partial derivatives
|
|
let (dpdu, dpdv, degenerate_uv, det) = self.compute_partials(data);
|
|
// Interpolate (u, v) parametric coordinates and hit point
|
|
let p_hit_vec =
|
|
ti.b0 * Vector3f::from(p0) + ti.b1 * Vector3f::from(p1) + ti.b2 * Vector3f::from(p2);
|
|
let p_hit = Point3f::from(p_hit_vec);
|
|
let uv_hit_vec =
|
|
ti.b0 * Vector2f::from(uv0) + ti.b1 * Vector2f::from(uv1) + ti.b2 * Vector2f::from(uv2);
|
|
let uv_hit = Point2f::from(uv_hit_vec);
|
|
|
|
// Return SurfaceInteraction for triangle hit>
|
|
let flip_normal = data.reverse_orientation ^ data.transform_swaps_handedness;
|
|
let p_abs_sum = (ti.b0 * Vector3f::from(p0)).abs()
|
|
+ (ti.b1 * Vector3f::from(p1)).abs()
|
|
+ (ti.b2 * Vector3f::from(p2)).abs();
|
|
let p_error = gamma(7) * p_abs_sum;
|
|
let mut isect = SurfaceInteraction::new(
|
|
Point3fi::new_with_error(p_hit, p_error),
|
|
uv_hit,
|
|
wo,
|
|
dpdu,
|
|
dpdv,
|
|
Normal3f::default(),
|
|
Normal3f::default(),
|
|
time,
|
|
flip_normal,
|
|
);
|
|
|
|
isect.face_index = self
|
|
.mesh()
|
|
.face_indices
|
|
.as_ref()
|
|
.map_or(0, |fi| fi[self.tri_index]);
|
|
isect.common.n = data.normal;
|
|
isect.shading.n = isect.n();
|
|
if flip_normal {
|
|
isect.common.n = -isect.n();
|
|
isect.shading.n = -isect.shading.n;
|
|
}
|
|
|
|
if data.normals.is_some() || self.mesh().s.is_some() {
|
|
self.apply_shading_normals(&mut isect, ti, data, degenerate_uv, det);
|
|
}
|
|
|
|
isect
|
|
}
|
|
|
|
fn compute_partials(&self, data: TriangleData) -> (Vector3f, Vector3f, bool, Float) {
|
|
let [p0, p1, p2] = data.vertices;
|
|
let [uv0, uv1, uv2] = data.uvs;
|
|
let duv02 = uv0 - uv2;
|
|
let duv12 = uv1 - uv2;
|
|
let dp02 = p0 - p2;
|
|
let dp12 = p1 - p2;
|
|
let det = difference_of_products(duv02[0], duv12[1], duv02[1], duv12[0]);
|
|
let degenerate_uv = det.abs() < 1e-9;
|
|
let (dpdu, dpdv) = if !degenerate_uv {
|
|
let inv_det = 1. / det;
|
|
(
|
|
(dp02 * duv12[1] - dp12 * duv02[1]) * inv_det,
|
|
(dp12 * duv02[0] - dp02 * duv12[0]) * inv_det,
|
|
)
|
|
} else {
|
|
let dp20 = p2 - p0;
|
|
let dp10 = p1 - p0;
|
|
let mut ng = dp20.cross(dp10);
|
|
if ng.norm_squared() == 0. {
|
|
ng = (dp20.cast::<f64>().cross(dp10.cast::<f64>())).cast();
|
|
}
|
|
let n = ng.normalize();
|
|
n.coordinate_system()
|
|
};
|
|
(dpdu, dpdv, degenerate_uv, det)
|
|
}
|
|
|
|
fn apply_shading_normals(
|
|
&self,
|
|
isect: &mut SurfaceInteraction,
|
|
ti: TriangleIntersection,
|
|
data: TriangleData,
|
|
degenerate_uv: bool,
|
|
det: Float,
|
|
) {
|
|
let Some([n0, n1, n2]) = data.normals else {
|
|
return;
|
|
};
|
|
let [uv0, uv1, uv2] = data.uvs;
|
|
let duv02 = uv0 - uv2;
|
|
let duv12 = uv1 - uv2;
|
|
|
|
let ns = ti.b0 * n0 + ti.b1 * n1 + ti.b2 * n2;
|
|
let ns = if ns.norm_squared() > 0. {
|
|
ns.normalize()
|
|
} else {
|
|
isect.n()
|
|
};
|
|
|
|
let mut ss = self.mesh().s.as_ref().map_or(isect.dpdu, |s| {
|
|
let indices = &self.mesh().vertex_indices[3 * self.tri_index..3 * self.tri_index + 3];
|
|
let interp_s = ti.b0 * s[indices[0]] + ti.b1 * s[indices[1]] + ti.b2 * s[indices[2]];
|
|
|
|
if interp_s.norm_squared() > 0. {
|
|
interp_s
|
|
} else {
|
|
isect.dpdu
|
|
}
|
|
});
|
|
|
|
let mut ts = Vector3f::from(ns).cross(ss);
|
|
if ts.norm_squared() > 0. {
|
|
ss = ts.cross(Vector3f::from(ns));
|
|
} else {
|
|
(ss, ts) = Vector3f::from(ns).coordinate_system();
|
|
}
|
|
let (dndu, dndv) = if degenerate_uv {
|
|
let dn = (n2 - n0).cross(n1 - n0);
|
|
if dn.norm_squared() == 0. {
|
|
(Normal3f::zero(), Normal3f::zero())
|
|
} else {
|
|
dn.coordinate_system()
|
|
}
|
|
} else {
|
|
let inv_det = 1. / det;
|
|
let dn02 = n0 - n2;
|
|
let dn12 = n1 - n2;
|
|
(
|
|
(dn02 * duv12[1] - dn12 * duv02[1]) * inv_det,
|
|
(dn12 * duv02[0] - dn02 * duv12[0]) * inv_det,
|
|
)
|
|
};
|
|
|
|
isect.shading.n = ns;
|
|
isect.shading.dpdu = ss;
|
|
isect.shading.dpdv = ts;
|
|
isect.dndu = dndu;
|
|
isect.dndv = dndv;
|
|
}
|
|
}
|
|
|
|
impl ShapeTrait for TriangleShape {
|
|
fn bounds(&self) -> Bounds3f {
|
|
let [p0, p1, p2] = self.get_data().vertices;
|
|
Bounds3f::from_points(p0, p1).union_point(p2)
|
|
}
|
|
|
|
fn normal_bounds(&self) -> DirectionCone {
|
|
let data = self.get_data();
|
|
let mut n = data.normal;
|
|
if let Some([n0, n1, n2]) = data.normals {
|
|
n = n.face_forward((n0 + n1 + n2).into());
|
|
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
|
|
n = -n;
|
|
}
|
|
DirectionCone::new_from_vector(Vector3f::from(n))
|
|
}
|
|
|
|
fn area(&self) -> Float {
|
|
self.get_data().area
|
|
}
|
|
|
|
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 (Self::MIN_SPHERICAL_SAMPLE_AREA..Self::MAX_SPHERICAL_SAMPLE_AREA).contains(&solid_angle)
|
|
{
|
|
let ray = ctx.spawn_ray(wi);
|
|
return self.intersect(&ray, None).map_or(0., |isect| {
|
|
let absdot = Vector3f::from(isect.intr.n()).dot(-wi).abs();
|
|
let d2 = ctx.p().distance_squared(isect.intr.p());
|
|
let pdf = 1. / self.area() * (d2 / absdot);
|
|
if pdf.is_infinite() { 0. } else { pdf }
|
|
});
|
|
}
|
|
|
|
let mut pdf = 1. / solid_angle;
|
|
if ctx.ns != Normal3f::zero() {
|
|
let [p0, p1, p2] = self.get_data().vertices;
|
|
let u = invert_spherical_triangle_sample(&[p0, p1, p2], ctx.p(), wi)
|
|
.unwrap_or(Point2f::zero());
|
|
|
|
let rp = ctx.p();
|
|
let wi: [Vector3f; 3] = [
|
|
(p0 - rp).normalize(),
|
|
(p1 - rp).normalize(),
|
|
(p2 - rp).normalize(),
|
|
];
|
|
let w: [Float; 4] = [
|
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
|
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()),
|
|
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()),
|
|
];
|
|
pdf *= bilinear_pdf(u, &w);
|
|
}
|
|
pdf
|
|
}
|
|
|
|
fn sample_from_context(&self, ctx: &ShapeSampleContext, u: Point2f) -> Option<ShapeSample> {
|
|
let data = self.get_data();
|
|
let [p0, p1, p2] = data.vertices;
|
|
let solid_angle = self.solid_angle(ctx.p());
|
|
if (Self::MIN_SPHERICAL_SAMPLE_AREA..Self::MAX_SPHERICAL_SAMPLE_AREA).contains(&solid_angle)
|
|
{
|
|
// Sample shape by area and compute incident direction wi
|
|
return self.sample(u).and_then(|mut ss| {
|
|
let mut intr_clone = (*ss.intr).clone();
|
|
intr_clone.common.time = ctx.time;
|
|
ss.intr = Arc::new(intr_clone);
|
|
|
|
let wi = (ss.intr.p() - ctx.p()).normalize();
|
|
if wi.norm_squared() == 0. {
|
|
return None;
|
|
}
|
|
let absdot = Vector3f::from(ss.intr.n()).abs_dot(-wi);
|
|
let d2 = ctx.p().distance_squared(ss.intr.p());
|
|
ss.pdf /= absdot / d2;
|
|
if ss.pdf.is_infinite() { None } else { Some(ss) }
|
|
});
|
|
}
|
|
|
|
// Sample spherical triangle from reference point
|
|
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] = [
|
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
|
0.01_f32.max(ctx.ns.dot(wi[1].into()).abs()),
|
|
0.01_f32.max(ctx.ns.dot(wi[0].into()).abs()),
|
|
0.01_f32.max(ctx.ns.dot(wi[2].into()).abs()),
|
|
];
|
|
|
|
let 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;
|
|
}
|
|
pdf *= tri_pdf;
|
|
let b2 = 1. - b[0] - b[1];
|
|
|
|
let p_abs_sum = b[0] * Vector3f::from(p0)
|
|
+ b[1] * Vector3f::from(p1)
|
|
+ (1. - b[0] - b[1]) * Vector3f::from(p2);
|
|
let p_error = gamma(6) * p_abs_sum;
|
|
// Return ShapeSample for solid angle sampled point on triangle
|
|
let p_vec =
|
|
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
|
|
let p = Point3f::from(p_vec);
|
|
let mut n = Normal3f::from((p1 - p0).cross(p2 - p0).normalize());
|
|
if let Some([n0, n1, n2]) = data.normals {
|
|
let ns = b[0] * n0 + b[1] * n1 + b2 * n2;
|
|
n = n.face_forward(ns.into());
|
|
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
|
|
n = -n;
|
|
}
|
|
|
|
let [uv0, uv1, uv2] = data.uvs;
|
|
let uv_sample_vec =
|
|
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2);
|
|
let uv_sample = Point2f::from(uv_sample_vec);
|
|
let pi = Point3fi::new_with_error(p, p_error);
|
|
let mut si = SurfaceInteraction::new_simple(pi, n, uv_sample);
|
|
si.common.time = ctx.time;
|
|
Some(ShapeSample {
|
|
intr: Arc::new(si),
|
|
pdf,
|
|
})
|
|
}
|
|
|
|
fn sample(&self, u: Point2f) -> Option<ShapeSample> {
|
|
let data = self.get_data();
|
|
let [p0, p1, p2] = data.vertices;
|
|
let [uv0, uv1, uv2] = data.uvs;
|
|
let b = sample_uniform_triangle(u);
|
|
let p_vec =
|
|
b[0] * Vector3f::from(p0) + b[1] * Vector3f::from(p1) + b[2] * Vector3f::from(p2);
|
|
let b2 = 1. - b[0] - b[1];
|
|
let p = Point3f::from(p_vec);
|
|
let mut n = data.normal;
|
|
if let Some([n0, n1, n2]) = data.normals {
|
|
let interp_n = b[0] * n0 + b[1] * n1 + b2 * n2;
|
|
n = n.face_forward(interp_n.into());
|
|
} else if data.reverse_orientation ^ data.transform_swaps_handedness {
|
|
n = -n;
|
|
}
|
|
|
|
let uv_sample_vec =
|
|
b[0] * Vector2f::from(uv0) + b[1] * Vector2f::from(uv1) + b[2] * Vector2f::from(uv2);
|
|
let uv_sample = Point2f::from(uv_sample_vec);
|
|
let p_abs_sum = (b[0] * Vector3f::from(p0)).abs()
|
|
+ (b[1] * Vector3f::from(p1)).abs()
|
|
+ ((1. - b[0] - b[1]) * Vector3f::from(p2)).abs();
|
|
let p_error = gamma(6) * p_abs_sum;
|
|
let pi = Point3fi::new_with_error(p, p_error);
|
|
Some(ShapeSample {
|
|
intr: Arc::new(SurfaceInteraction::new_simple(pi, n, uv_sample)),
|
|
pdf: 1. / self.area(),
|
|
})
|
|
}
|
|
|
|
fn intersect(&self, ray: &Ray, t_max: Option<Float>) -> Option<ShapeIntersection> {
|
|
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY))
|
|
.map(|ti| {
|
|
let intr = self.interaction_from_intersection(ti, ray.time, -ray.d);
|
|
ShapeIntersection { intr, t_hit: ti.t }
|
|
})
|
|
}
|
|
|
|
fn intersect_p(&self, ray: &Ray, t_max: Option<Float>) -> bool {
|
|
self.intersect_triangle(ray, t_max.unwrap_or(Float::INFINITY))
|
|
.is_some()
|
|
}
|
|
}
|