mesh_rand/meshsurface.rs
1use crate::vecmath as m;
2use rand_distr::weighted_alias::WeightedAliasIndex;
3use rand_distr::Distribution;
4use thiserror::Error;
5
6#[derive(Error, Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
7pub enum MError {
8 #[error("failed to initialize: {0}")]
9 Initialization(String),
10}
11
12#[derive(Debug, Copy, Clone)]
13struct Triangle {
14 origin: m::Vector,
15 normal: m::Vector,
16 u: m::Vector,
17 v: m::Vector,
18}
19
20/// A distribution for sampling points uniformly on the surface of a 3d model
21///
22/// Samples the surface of a model by first randomly picking a triangle with probability
23/// proportional to its area, and then uniformly samples a point within that triangle.
24///
25/// # Example
26///
27/// ```
28/// use mesh_rand::MeshSurface;
29/// use rand::distributions::Distribution;
30///
31/// # fn main() -> Result<(), Box<dyn std::error::Error>> {
32/// let verticies = [
33/// [1.0, 0.0, 0.0],
34/// [0.0, 1.0, 0.0],
35/// [0.0, 0.0, 1.0],
36/// [1.0, 2.0, 0.0],
37/// ];
38/// let faces = [[0, 1, 2], [0, 1, 3]];
39/// let mesh_dist = MeshSurface::new(&verticies, &faces)?;
40/// let mut rng = rand::thread_rng();
41/// let sample = mesh_dist.sample(&mut rng);
42/// println!(
43/// "generated point on mesh at {:?} located on face with index {:?} with normal {:?}",
44/// sample.position, sample.face_index, sample.normal
45/// );
46/// # Ok(())
47/// # }
48/// ```
49///
50
51#[derive(Debug, Clone)]
52pub struct MeshSurface {
53 triangles: Vec<Triangle>,
54 triangle_dist: WeightedAliasIndex<f32>,
55}
56
57impl MeshSurface {
58 /// Initializes a new mesh surface distribution given verticies and faces (triangles)
59 ///
60 /// # Result
61 /// Returns an error if:
62 /// * An index defining a face is out of range of the verticies collection
63 /// * The area of one of the triangles provided is very close to 0 (`f32::is_normal(area) == false`)
64 /// * The collection of faces is empty
65 pub fn new(verts: &[m::Vector], faces: &[[usize; 3]]) -> Result<Self, MError> {
66 let mut triangles = Vec::with_capacity(faces.len());
67 let mut triangle_areas = Vec::with_capacity(faces.len());
68 let ind_err = |f_id, i| {
69 MError::Initialization(format!(
70 "face at index {} referenced vert index {} which is out of range (vert.len() = {})",
71 f_id,
72 i,
73 verts.len()
74 ))
75 };
76 for (f_id, &[i1, i2, i3]) in faces.iter().enumerate() {
77 let (p1, p2, p3) = (
78 *verts.get(i1).ok_or_else(|| ind_err(f_id, i1))?,
79 *verts.get(i2).ok_or_else(|| ind_err(f_id, i2))?,
80 *verts.get(i3).ok_or_else(|| ind_err(f_id, i3))?,
81 );
82 let origin = p1;
83 let u = m::diff(p2, p1);
84 let v = m::diff(p3, p1);
85 let normal_dir = m::cross(u, v);
86 let len = m::len(normal_dir);
87 let area = len / 2.0;
88 if !f32::is_normal(area) {
89 return Err(MError::Initialization(format!(
90 "area of face at index {} too close to 0 (f32::is_normal(area) == false)",
91 f_id
92 )));
93 }
94 let normal = m::div(normal_dir, len);
95 triangle_areas.push(area);
96 triangles.push(Triangle {
97 origin,
98 u,
99 v,
100 normal,
101 })
102 }
103 let triangle_dist = WeightedAliasIndex::new(triangle_areas).map_err(|_| {
104 //cases of trangle area being close to 0 handled above, must be empty
105 MError::Initialization("faces array is embty".into())
106 })?;
107 Ok(MeshSurface {
108 triangles,
109 triangle_dist,
110 })
111 }
112}
113
114/// Surface sample returned from surface distributions
115#[derive(Clone, Copy, Debug, PartialEq, PartialOrd)]
116pub struct SurfSample {
117 /// Generated point on the model surface
118 pub position: m::Vector,
119 /// Normalized normal vector of the triangle the point resides in.
120 ///
121 /// OBS: The normal will be pointing out of the positively oriented side of the triangle. As
122 /// an example, the triangle
123 /// defined by the verticies `[a, b, c]` where `a = [0.0, 0.0, 0.0]`, `b = [1.0, 0.0, 0.0]` and
124 /// `c = [0.0, 1.0, 0.0]` has the normal `[0.0, 0.0, 1.0]``. While a triangle defined by
125 /// `[b, a, c]` has the normal `[0.0, 0.0, -1.0]`.
126 pub normal: m::Vector,
127 /// Index of the triangle the point resides in, in the face slice used for initialization
128 pub face_index: usize,
129}
130
131impl Distribution<SurfSample> for MeshSurface {
132 /// Samples the model surface uniformly, returning an instance of the [SurfSample] struct
133 fn sample<R: rand::Rng + ?Sized>(&self, rng: &mut R) -> SurfSample {
134 let t_ind = self.triangle_dist.sample(rng);
135 let Triangle {
136 origin,
137 u,
138 v,
139 normal,
140 } = self.triangles[t_ind];
141 let mut v_rand = rng.gen_range(0.0..=1.0);
142 let mut u_rand = rng.gen_range(0.0..=1.0);
143 if v_rand + u_rand > 1.0 {
144 v_rand = 1.0 - v_rand;
145 u_rand = 1.0 - u_rand;
146 }
147 let point = m::add(origin, m::add(m::mul(v, v_rand), m::mul(u, u_rand)));
148 SurfSample {
149 position: point,
150 face_index: t_ind,
151 normal,
152 }
153 }
154}