use nalgebra as na;
use rand::{Rng, RngExt};
use crate::mesh::Mesh;
#[derive(Debug, Clone)]
pub struct SurfacePoint {
pub position: na::Point3<f32>,
pub normal: na::Vector3<f32>,
pub face_index: u32,
pub barycentric: [f32; 3],
}
#[must_use]
pub fn sample_mesh_surface(mesh: &Mesh, count: usize, rng: &mut impl Rng) -> Vec<SurfacePoint> {
if mesh.faces.is_empty() || count == 0 {
return Vec::new();
}
let areas: Vec<f32> = mesh.faces.iter().map(|f| mesh.face_area(f)).collect();
let total_area: f32 = areas.iter().sum();
if total_area < 1e-12 {
return Vec::new();
}
let mut cdf = Vec::with_capacity(areas.len());
let mut cumulative = 0.0f32;
for &a in &areas {
cumulative += a / total_area;
cdf.push(cumulative);
}
if let Some(last) = cdf.last_mut() {
*last = 1.0;
}
let mut points = Vec::with_capacity(count);
for _ in 0..count {
let r: f32 = rng.random();
let face_idx = cdf.partition_point(|&x| x < r).min(mesh.faces.len() - 1);
let face = &mesh.faces[face_idx];
let v0 = &mesh.vertices[face[0] as usize];
let v1 = &mesh.vertices[face[1] as usize];
let v2 = &mesh.vertices[face[2] as usize];
let n0 = &mesh.normals[face[0] as usize];
let n1 = &mesh.normals[face[1] as usize];
let n2 = &mesh.normals[face[2] as usize];
let r1: f32 = rng.random::<f32>().sqrt();
let r2: f32 = rng.random();
let u = 1.0 - r1;
let v = r2 * r1;
let w = 1.0 - u - v;
let position = na::Point3::from(v0.coords * u + v1.coords * v + v2.coords * w);
let normal = (n0 * u + n1 * v + n2 * w).normalize();
points.push(SurfacePoint {
position,
normal,
face_index: face_idx as u32,
barycentric: [u, v, w],
});
}
points
}
#[cfg(test)]
mod tests {
use super::*;
use rand::SeedableRng;
fn unit_triangle() -> Mesh {
Mesh::new(
vec![
na::Point3::new(0.0, 0.0, 0.0),
na::Point3::new(1.0, 0.0, 0.0),
na::Point3::new(0.0, 1.0, 0.0),
],
vec![[0, 1, 2]],
)
}
#[test]
fn sampled_points_are_on_surface() {
let mesh = unit_triangle();
let mut rng = rand::rngs::StdRng::seed_from_u64(42);
let points = sample_mesh_surface(&mesh, 100, &mut rng);
assert_eq!(points.len(), 100);
for p in &points {
assert!(p.position.x >= -1e-6, "x = {}", p.position.x);
assert!(p.position.y >= -1e-6, "y = {}", p.position.y);
assert!(
p.position.x + p.position.y <= 1.0 + 1e-6,
"x+y = {}",
p.position.x + p.position.y
);
assert!(p.position.z.abs() < 1e-6);
let sum: f32 = p.barycentric.iter().sum();
assert!((sum - 1.0).abs() < 1e-5, "bary sum = {sum}");
}
}
}