blender_mesh/combine_indices/
weighted_normals.rs1use crate::SingleIndexedVertexAttributes;
2use nalgebra::{Point3, Vector3};
3use std::collections::hash_map::Entry;
4use std::collections::HashMap;
5
6#[derive(Debug, thiserror::Error)]
8pub enum WeightedNormalsError {
9 #[error("There were no normals to weight")]
10 NoNormals,
11}
12
13impl SingleIndexedVertexAttributes {
14 pub fn face_weight_normals(&mut self) -> Result<(), WeightedNormalsError> {
28 let mut encountered_positions: HashMap<[u32; 3], SharedVertexPositionWeightedNormal> =
29 HashMap::new();
30
31 for (vertex_num, pos_norm_data_idx) in self.indices.iter().enumerate() {
32 let pos_norm_data_idx = *pos_norm_data_idx as usize;
33
34 let vertex = self.vertices()[pos_norm_data_idx];
35
36 let pos = vertex.position;
37 let pos_point = Point3::new(pos[0], pos[1], pos[2]);
38
39 let face_normal = vertex.normal.unwrap();
40 let face_normal = Vector3::new(face_normal[0], face_normal[1], face_normal[2]);
41
42 let (connected_vertex_1, connected_vertex_2) = match vertex_num % 3 {
43 0 => (vertex_num + 1, vertex_num + 2),
44 1 => (vertex_num - 1, vertex_num + 1),
45 2 => (vertex_num - 2, vertex_num - 1),
46 _ => unreachable!(),
47 };
48
49 let connected_vertex_1 =
50 self.vertices[self.indices[connected_vertex_1] as usize].position;
51 let connected_vertex_1 = Point3::new(
52 connected_vertex_1[0],
53 connected_vertex_1[1],
54 connected_vertex_1[2],
55 );
56
57 let connected_vertex_2 =
58 self.vertices[self.indices[connected_vertex_2] as usize].position;
59 let connected_vertex_2 = Point3::new(
60 connected_vertex_2[0],
61 connected_vertex_2[1],
62 connected_vertex_2[2],
63 );
64
65 let weighted_normal = weight_normal_using_surface_and_angle(
66 face_normal,
67 connected_vertex_1 - pos_point.clone(),
68 connected_vertex_2 - pos_point,
69 );
70
71 let pos_hash = [pos[0].to_bits(), pos[1].to_bits(), pos[2].to_bits()];
72 match encountered_positions.entry(pos_hash) {
73 Entry::Occupied(mut previous) => {
74 previous
75 .get_mut()
76 .normals_to_overwrite
77 .push(pos_norm_data_idx);
78 previous.get_mut().weighted_normal += weighted_normal;
79 }
80 Entry::Vacant(vacant) => {
81 vacant.insert(SharedVertexPositionWeightedNormal {
82 normals_to_overwrite: vec![pos_norm_data_idx],
83 weighted_normal,
84 });
85 }
86 };
87 }
88
89 for (_pos_hash, overlapping_vertices) in encountered_positions.into_iter() {
90 let weighted_normal = overlapping_vertices.weighted_normal.normalize();
91 let weighted_normal = weighted_normal.as_slice();
92
93 for normal_data_idx in overlapping_vertices.normals_to_overwrite {
94 let mut normal = [0.; 3];
95 normal.copy_from_slice(weighted_normal);
96
97 self.vertices_mut()[normal_data_idx].normal = Some(normal);
98 }
99 }
100
101 Ok(())
102 }
103}
104
105#[derive(Debug)]
110struct SharedVertexPositionWeightedNormal {
111 normals_to_overwrite: Vec<usize>,
112 weighted_normal: Vector3<f32>,
113}
114
115fn weight_normal_using_surface_and_angle(
125 face_normal: Vector3<f32>,
126 connected_face_edge_1: Vector3<f32>,
127 connected_face_edge_2: Vector3<f32>,
128) -> Vector3<f32> {
129 let face_normal = face_normal.normalize();
130
131 let angle = connected_face_edge_1.angle(&connected_face_edge_2);
132
133 let area =
134 0.5 * connected_face_edge_1.magnitude() * connected_face_edge_2.magnitude() * angle.sin();
135
136 face_normal * area * angle
137}
138
139#[cfg(test)]
140mod tests {
141 use super::*;
142 use crate::Vertex;
143 use std::f32::consts::PI;
144
145 #[test]
147 fn calculate_weighted_normal() {
148 let input_normal: Vector3<f32> = [0., 1., 0.].into();
149
150 let connected_face_edge_1 = [1., 0., 0.].into();
151 let connected_face_edge_2 = [0., 0., 1.].into();
152
153 let expected_angle = PI / 2.;
154 let expected_area = 0.5;
155
156 let weighted_normal = weight_normal_using_surface_and_angle(
157 input_normal.clone(),
158 connected_face_edge_1,
159 connected_face_edge_2,
160 );
161
162 assert_eq!(
163 weighted_normal,
164 input_normal * expected_area * expected_angle
165 );
166 }
167
168 #[test]
173 fn weighted_normals_one_triangle() {
174 let indices = vec![0, 1, 2];
175
176 #[rustfmt::skip]
177 let positions = vec![
178 0., 0., 0.,
179 1., 0., 0.,
180 0., 1., 0.,
181 ];
182
183 #[rustfmt::skip]
184 let normals = vec![
185 0., 1., 0.,
186 1., 0., 0.,
187 0., 1., 0.,
188 ];
189
190 let mut vertices = vec![];
191 for idx in 0..positions.len() / 3 {
192 vertices.push(Vertex {
193 position: [
194 positions[idx * 3],
195 positions[idx * 3 + 1],
196 positions[idx * 3 + 2],
197 ],
198 normal: Some([normals[idx * 3], normals[idx * 3 + 1], normals[idx * 3 + 2]]),
199 ..Vertex::default()
200 });
201 }
202
203 let mut single_indexed = SingleIndexedVertexAttributes {
204 indices,
205 vertices,
206 ..SingleIndexedVertexAttributes::default()
207 };
208 single_indexed.face_weight_normals().unwrap();
209
210 let face_weighted_normals: Vec<f32> = single_indexed
211 .vertices()
212 .iter()
213 .flat_map(|v| v.normal.unwrap().to_vec())
214 .collect();
215
216 assert_eq!(face_weighted_normals, normals);
217 }
218
219 #[test]
231 fn weights_two_normals() {
232 let mut single_indexed = create_single_indexed();
233 single_indexed.face_weight_normals().unwrap();
234
235 let expected_weighted_norm = get_expected_weighted_norm();
236
237 let normals: Vec<f32> = single_indexed
238 .vertices()
239 .iter()
240 .flat_map(|v| v.normal.unwrap().to_vec())
241 .collect();
242
243 let actual_normals = normals;
244 assert_eq!(&actual_normals[0..3], &expected_weighted_norm);
245 assert_eq!(&actual_normals[12..15], &expected_weighted_norm);
246 }
247
248 fn get_expected_weighted_norm() -> [f32; 3] {
249 let angle = PI / 2.;
250
251 let area = 1. * 1. / 2.;
252 let first_triangle_contrib = Vector3::new(0., 1., 0.) * area * angle;
253
254 let area = 10. * 10. / 2.;
255 let second_triangle_contrib = Vector3::new(1., 0., 0.) * area * angle;
256
257 let weighted_normal = (first_triangle_contrib + second_triangle_contrib).normalize();
258 let expected_weighted_norm = [weighted_normal[0], weighted_normal[1], weighted_normal[2]];
259
260 expected_weighted_norm
261 }
262
263 fn create_single_indexed() -> SingleIndexedVertexAttributes {
264 let indices = vec![0, 1, 2, 3, 4, 5];
266
267 #[rustfmt::skip]
268 let positions = vec![
269 0., 0., 0.,
270 1., 0., 0.,
271 0., 1., 0.,
272 10., 0., 0.,
273 0., 0., 0.,
274 0., 10., 0.,
275 ];
276
277 #[rustfmt::skip]
278 let normals = vec![
279 0., 1., 0.,
280 0., 0., 0.,
281 0., 0., 0.,
282 0., 0., 0.,
283 1., 0., 0.,
284 0., 0., 0.,
285 ];
286
287 let mut vertices = vec![];
288 for idx in 0..positions.len() / 3 {
289 vertices.push(Vertex {
290 position: [
291 positions[idx * 3],
292 positions[idx * 3 + 1],
293 positions[idx * 3 + 2],
294 ],
295 normal: Some([normals[idx * 3], normals[idx * 3 + 1], normals[idx * 3 + 2]]),
296 ..Vertex::default()
297 });
298 }
299
300 let single_indexed = SingleIndexedVertexAttributes {
301 indices,
302 vertices,
303 ..SingleIndexedVertexAttributes::default()
304 };
305 single_indexed
306 }
307}