#![allow(dead_code)] use std::{f32::consts::FRAC_PI_4 as QPI_32, f32::consts::PI as PI_32, f64::consts::PI as PI_64}; use crate::math::{cross, dot, Point, Vector}; /// Maps the unit square to the unit circle. /// NOTE: x and y should be distributed within [-1, 1], /// not [0, 1]. pub fn square_to_circle(x: f32, y: f32) -> (f32, f32) { debug_assert!(x >= -1.0 && x <= 1.0 && y >= -1.0 && y <= 1.0); if x == 0.0 && y == 0.0 { return (0.0, 0.0); } let (radius, angle) = if x > y.abs() { // Quadrant 1 (x, QPI_32 * (y / x)) } else if y > x.abs() { // Quadrant 2 (y, QPI_32 * (2.0 - (x / y))) } else if x < -(y.abs()) { // Quadrant 3 (-x, QPI_32 * (4.0 + (y / x))) } else { // Quadrant 4 (-y, QPI_32 * (6.0 - (x / y))) }; (radius * angle.cos(), radius * angle.sin()) } pub fn cosine_sample_hemisphere(u: f32, v: f32) -> Vector { let (u, v) = square_to_circle((u * 2.0) - 1.0, (v * 2.0) - 1.0); let z = (1.0 - ((u * u) + (v * v))).max(0.0).sqrt(); Vector::new(u, v, z) } pub fn uniform_sample_hemisphere(u: f32, v: f32) -> Vector { let z = u; let r = (1.0 - (z * z)).max(0.0).sqrt(); let phi = 2.0 * PI_32 * v; let x = r * phi.cos(); let y = r * phi.sin(); Vector::new(x, y, z) } pub fn uniform_sample_sphere(u: f32, v: f32) -> Vector { let z = 1.0 - (2.0 * u); let r = (1.0 - (z * z)).max(0.0).sqrt(); let phi = 2.0 * PI_32 * v; let x = r * phi.cos(); let y = r * phi.sin(); Vector::new(x, y, z) } /// Samples a solid angle defined by a cone originating from (0,0,0) /// and pointing down the positive z-axis. /// /// `u`, `v`: sampling variables, should each be in the interval [0,1] /// `cos_theta_max`: cosine of the max angle from the z-axis, defining /// the outer extent of the cone. pub fn uniform_sample_cone(u: f32, v: f32, cos_theta_max: f64) -> Vector { let cos_theta = (1.0 - u as f64) + (u as f64 * cos_theta_max); let sin_theta = (1.0 - (cos_theta * cos_theta)).sqrt(); let phi = v as f64 * 2.0 * PI_64; Vector::new( (phi.cos() * sin_theta) as f32, (phi.sin() * sin_theta) as f32, cos_theta as f32, ) } pub fn uniform_sample_cone_pdf(cos_theta_max: f64) -> f64 { // 1.0 / solid angle 1.0 / (2.0 * PI_64 * (1.0 - cos_theta_max)) } /// Generates a uniform sample on a triangle given two uniform random /// variables i and j in [0, 1]. pub fn uniform_sample_triangle(va: Vector, vb: Vector, vc: Vector, i: f32, j: f32) -> Vector { let isqrt = i.sqrt(); let a = 1.0 - isqrt; let b = isqrt * (1.0 - j); let c = j * isqrt; (va * a) + (vb * b) + (vc * c) } /// Calculates the surface area of a triangle. pub fn triangle_surface_area(p0: Point, p1: Point, p2: Point) -> f32 { 0.5 * cross(p1 - p0, p2 - p0).length() } /// Calculates the projected solid angle of a spherical triangle. /// /// A, B, and C are the points of the triangle on a unit sphere. pub fn spherical_triangle_solid_angle(va: Vector, vb: Vector, vc: Vector) -> f32 { // Calculate sines and cosines of the spherical triangle's edge lengths let cos_a: f64 = dot(vb, vc).max(-1.0).min(1.0) as f64; let cos_b: f64 = dot(vc, va).max(-1.0).min(1.0) as f64; let cos_c: f64 = dot(va, vb).max(-1.0).min(1.0) as f64; let sin_a: f64 = (1.0 - (cos_a * cos_a)).sqrt(); let sin_b: f64 = (1.0 - (cos_b * cos_b)).sqrt(); let sin_c: f64 = (1.0 - (cos_c * cos_c)).sqrt(); // If two of the vertices are coincident, area is zero. // Return early to avoid a divide by zero below. if cos_a == 1.0 || cos_b == 1.0 || cos_c == 1.0 { return 0.0; } // Calculate the cosine of the angles at the vertices let cos_va = ((cos_a - (cos_b * cos_c)) / (sin_b * sin_c)) .max(-1.0) .min(1.0); let cos_vb = ((cos_b - (cos_c * cos_a)) / (sin_c * sin_a)) .max(-1.0) .min(1.0); let cos_vc = ((cos_c - (cos_a * cos_b)) / (sin_a * sin_b)) .max(-1.0) .min(1.0); // Calculate the angles themselves, in radians let ang_va = cos_va.acos(); let ang_vb = cos_vb.acos(); let ang_vc = cos_vc.acos(); // Calculate and return the solid angle of the triangle (ang_va + ang_vb + ang_vc - PI_64) as f32 } /// Generates a uniform sample on a spherical triangle given two uniform /// random variables i and j in [0, 1]. pub fn uniform_sample_spherical_triangle( va: Vector, vb: Vector, vc: Vector, i: f32, j: f32, ) -> Vector { // Calculate sines and cosines of the spherical triangle's edge lengths let cos_a: f64 = dot(vb, vc).max(-1.0).min(1.0) as f64; let cos_b: f64 = dot(vc, va).max(-1.0).min(1.0) as f64; let cos_c: f64 = dot(va, vb).max(-1.0).min(1.0) as f64; let sin_a: f64 = (1.0 - (cos_a * cos_a)).sqrt(); let sin_b: f64 = (1.0 - (cos_b * cos_b)).sqrt(); let sin_c: f64 = (1.0 - (cos_c * cos_c)).sqrt(); // If two of the vertices are coincident, area is zero. // Return early to avoid a divide by zero below. if cos_a == 1.0 || cos_b == 1.0 || cos_c == 1.0 { // TODO: do something more intelligent here, in the case that it's // an infinitely thin line. return va; } // Calculate the cosine of the angles at the vertices let cos_va = ((cos_a - (cos_b * cos_c)) / (sin_b * sin_c)) .max(-1.0) .min(1.0); let cos_vb = ((cos_b - (cos_c * cos_a)) / (sin_c * sin_a)) .max(-1.0) .min(1.0); let cos_vc = ((cos_c - (cos_a * cos_b)) / (sin_a * sin_b)) .max(-1.0) .min(1.0); // Calculate sine for A let sin_va = (1.0 - (cos_va * cos_va)).sqrt(); // Calculate the angles themselves, in radians let ang_va = cos_va.acos(); let ang_vb = cos_vb.acos(); let ang_vc = cos_vc.acos(); // Calculate the area of the spherical triangle let area = ang_va + ang_vb + ang_vc - PI_64; // The rest of this is from the paper "Stratified Sampling of Spherical // Triangles" by James Arvo. let area_2 = area * i as f64; let s = (area_2 - ang_va).sin(); let t = (area_2 - ang_va).cos(); let u = t - cos_va; let v = s + (sin_va * cos_c); let q_top = (((v * t) - (u * s)) * cos_va) - v; let q_bottom = ((v * s) + (u * t)) * sin_va; let q = q_top / q_bottom; let vc_2 = (va * q as f32) + ((vc - (va * dot(vc, va))).normalized() * (1.0 - (q * q)).sqrt() as f32); let z = 1.0 - (j * (1.0 - dot(vc_2, vb))); (vb * z) + ((vc_2 - (vb * dot(vc_2, vb))).normalized() * (1.0 - (z * z)).sqrt()) }