viewport_lib/quantities/
one_forms.rs1use crate::GlyphItem;
27
28pub fn edge_one_form_to_glyphs(
45 positions: &[[f32; 3]],
46 indices: &[u32],
47 edge_values: &[f32],
48 scale: f32,
49) -> GlyphItem {
50 let num_tris = indices.len() / 3;
51 let n = num_tris.min(edge_values.len() / 3);
52
53 let mut glyph_positions = Vec::with_capacity(n);
54 let mut glyph_vectors = Vec::with_capacity(n);
55
56 for tri in 0..n {
57 let i0 = indices[3 * tri] as usize;
58 let i1 = indices[3 * tri + 1] as usize;
59 let i2 = indices[3 * tri + 2] as usize;
60
61 if i0 >= positions.len() || i1 >= positions.len() || i2 >= positions.len() {
62 continue;
63 }
64
65 let p0 = glam::Vec3::from(positions[i0]);
66 let p1 = glam::Vec3::from(positions[i1]);
67 let p2 = glam::Vec3::from(positions[i2]);
68
69 let e01 = p1 - p0;
70 let e12 = p2 - p1;
71 let e20 = p0 - p2;
72
73 let n_raw = e01.cross(-e20); let area2 = n_raw.length();
76
77 if area2 < 1e-12 {
78 continue; }
80
81 let face_normal = n_raw / area2; let w01 = edge_values[3 * tri];
84 let w12 = edge_values[3 * tri + 1];
85 let w20 = edge_values[3 * tri + 2];
86
87 let f = (w01 * face_normal.cross(e01)
89 + w12 * face_normal.cross(e12)
90 + w20 * face_normal.cross(e20))
91 / area2; let centroid = (p0 + p1 + p2) / 3.0;
94
95 glyph_positions.push(centroid.to_array());
96 glyph_vectors.push(f.to_array());
97 }
98
99 let mut item = GlyphItem::default();
100 item.positions = glyph_positions;
101 item.vectors = glyph_vectors;
102 item.scale = scale;
103 item
104}
105
106#[cfg(test)]
107mod tests {
108 use super::*;
109
110 fn xy_triangle() -> (Vec<[f32; 3]>, Vec<u32>) {
112 (
113 vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0], [0.0, 1.0, 0.0]],
114 vec![0, 1, 2],
115 )
116 }
117
118 #[test]
119 fn zero_edge_values_produce_zero_vector() {
120 let (pos, idx) = xy_triangle();
121 let edge_values = vec![0.0, 0.0, 0.0];
122 let item = edge_one_form_to_glyphs(&pos, &idx, &edge_values, 1.0);
123 assert_eq!(item.vectors.len(), 1);
124 for c in &item.vectors[0] {
125 assert!(c.abs() < 1e-6);
126 }
127 }
128
129 #[test]
130 fn linearity_scaling_edge_values_scales_output() {
131 let (pos, idx) = xy_triangle();
132 let ev1 = vec![1.0, 0.5, -0.5];
133 let ev2: Vec<f32> = ev1.iter().map(|v| v * 3.0).collect();
134 let item1 = edge_one_form_to_glyphs(&pos, &idx, &ev1, 1.0);
135 let item2 = edge_one_form_to_glyphs(&pos, &idx, &ev2, 1.0);
136 for i in 0..3 {
137 assert!(
138 (item2.vectors[0][i] - item1.vectors[0][i] * 3.0).abs() < 1e-4,
139 "linearity failed on component {i}"
140 );
141 }
142 }
143
144 #[test]
145 fn output_position_is_centroid() {
146 let (pos, idx) = xy_triangle();
147 let edge_values = vec![1.0, 0.0, 0.0];
148 let item = edge_one_form_to_glyphs(&pos, &idx, &edge_values, 1.0);
149 let c = item.positions[0];
150 let expected = [1.0 / 3.0, 1.0 / 3.0, 0.0];
151 for i in 0..3 {
152 assert!((c[i] - expected[i]).abs() < 1e-4);
153 }
154 }
155
156 #[test]
157 fn degenerate_triangle_skipped() {
158 let pos = vec![[0.0; 3]; 3];
159 let idx = vec![0u32, 1, 2];
160 let edge_values = vec![1.0, 1.0, 1.0];
161 let item = edge_one_form_to_glyphs(&pos, &idx, &edge_values, 1.0);
162 assert!(item.positions.is_empty());
163 }
164
165 #[test]
166 fn out_of_bounds_indices_skipped() {
167 let pos = vec![[0.0, 0.0, 0.0], [1.0, 0.0, 0.0]];
168 let idx = vec![0u32, 1, 99];
169 let edge_values = vec![1.0, 1.0, 1.0];
170 let item = edge_one_form_to_glyphs(&pos, &idx, &edge_values, 1.0);
171 assert!(item.positions.is_empty());
172 }
173
174 #[test]
175 fn scale_forwarded() {
176 let (pos, idx) = xy_triangle();
177 let item = edge_one_form_to_glyphs(&pos, &idx, &[1.0, 0.0, 0.0], 5.0);
178 assert!((item.scale - 5.0).abs() < 1e-6);
179 }
180
181 #[test]
182 fn short_edge_values_truncates() {
183 let (pos, idx) = xy_triangle();
184 let item = edge_one_form_to_glyphs(&pos, &idx, &[1.0, 0.0], 1.0);
186 assert!(item.positions.is_empty());
187 }
188}