1use serde_json::Value;
21
22use crate::error::DecodeError;
23use crate::json;
24use crate::math::{cross, normalize_or_zero, sub, Vec3};
25
26pub const ATTRIBUTE: &str = "usd::usdgeom::mesh";
28
29#[derive(Debug, Clone, PartialEq)]
33pub struct TriangleMesh {
34 pub positions: Vec<Vec3>,
36 pub indices: Vec<u32>,
39 pub face_normals: Vec<Vec3>,
44}
45
46impl TriangleMesh {
47 pub fn from_attribute(value: &Value) -> Result<Self, DecodeError> {
63 let object = json::object(value, ATTRIBUTE)?;
64 let positions = json::points3(json::field(object, "points")?, "points")?;
65 let indices = json::indices(
66 json::field(object, "faceVertexIndices")?,
67 "faceVertexIndices",
68 )?;
69 if let Some(counts) = object.get("faceVertexCounts") {
70 let counts = json::counts(counts, "faceVertexCounts")?;
71 if let Some((face, &n)) = counts.iter().enumerate().find(|(_, &n)| n != 3) {
72 return Err(DecodeError::NonTriangleFace {
73 face,
74 vertex_count: n,
75 });
76 }
77 let counted = counts.len() as u64 * 3;
78 if counted != indices.len() as u64 {
79 return Err(DecodeError::CountMismatch {
80 field: "faceVertexCounts",
81 counted,
82 available: indices.len(),
83 });
84 }
85 }
86 Self::new(positions, indices)
87 }
88
89 pub fn new(positions: Vec<Vec3>, indices: Vec<u32>) -> Result<Self, DecodeError> {
92 if indices.len() % 3 != 0 {
93 return Err(DecodeError::NotTriangles {
94 index_count: indices.len(),
95 });
96 }
97 if let Some((i, &index)) = indices
98 .iter()
99 .enumerate()
100 .find(|(_, &index)| index as usize >= positions.len())
101 {
102 return Err(DecodeError::IndexOutOfRange {
103 at: format!("faceVertexIndices[{i}]"),
104 index,
105 vertex_count: positions.len(),
106 });
107 }
108 let face_normals = indices
109 .chunks_exact(3)
110 .map(|t| {
111 let [a, b, c] = [0, 1, 2].map(|k| positions[t[k] as usize]);
112 normalize_or_zero(cross(sub(b, a), sub(c, a)))
113 })
114 .collect();
115 Ok(Self {
116 positions,
117 indices,
118 face_normals,
119 })
120 }
121
122 pub fn triangle_count(&self) -> usize {
124 self.indices.len() / 3
125 }
126
127 pub fn triangles(&self) -> impl Iterator<Item = [u32; 3]> + '_ {
129 self.indices.chunks_exact(3).map(|t| [t[0], t[1], t[2]])
130 }
131}
132
133#[cfg(test)]
134mod tests {
135 use super::*;
136 use serde_json::json;
137
138 fn cube() -> Value {
140 json!({
141 "points": [
142 [0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0],
143 [0, 0, 1], [1, 0, 1], [1, 1, 1], [0, 1, 1]
144 ],
145 "faceVertexIndices": [
146 0, 2, 1, 0, 3, 2,
147 4, 5, 6, 4, 6, 7,
148 0, 1, 5, 0, 5, 4,
149 1, 2, 6, 1, 6, 5,
150 2, 3, 7, 2, 7, 6,
151 3, 0, 4, 3, 4, 7
152 ]
153 })
154 }
155
156 #[test]
157 fn cube_has_8_positions_and_36_indices() {
158 let mesh = TriangleMesh::from_attribute(&cube()).unwrap();
159 assert_eq!(mesh.positions.len(), 8);
160 assert_eq!(mesh.indices.len(), 36);
161 assert_eq!(mesh.triangle_count(), 12);
162 assert_eq!(mesh.face_normals.len(), 12);
163 let expected = [
164 [0.0, 0.0, -1.0],
165 [0.0, 0.0, 1.0],
166 [0.0, -1.0, 0.0],
167 [1.0, 0.0, 0.0],
168 [0.0, 1.0, 0.0],
169 [-1.0, 0.0, 0.0],
170 ];
171 for (i, n) in mesh.face_normals.iter().enumerate() {
172 assert_eq!(*n, expected[i / 2], "triangle {i}");
173 }
174 assert_eq!(mesh.triangles().next(), Some([0, 2, 1]));
175 }
176
177 #[test]
178 fn accepts_integer_and_float_coordinates() {
179 let mesh = TriangleMesh::from_attribute(&json!({
180 "points": [[0, 0.5, 0], [2, 0, 0], [0, 0, 1e3]],
181 "faceVertexIndices": [0, 1, 2]
182 }))
183 .unwrap();
184 assert_eq!(mesh.positions[2], [0.0, 0.0, 1000.0]);
185 }
186
187 #[test]
188 fn degenerate_triangle_has_zero_normal() {
189 let mesh = TriangleMesh::new(vec![[0.0; 3], [1.0, 0.0, 0.0]], vec![0, 1, 1]).unwrap();
190 assert_eq!(mesh.face_normals, vec![[0.0; 3]]);
191 }
192
193 #[test]
194 fn out_of_range_index_is_an_error() {
195 let e = TriangleMesh::from_attribute(&json!({
196 "points": [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
197 "faceVertexIndices": [0, 1, 2, 2, 1, 3]
198 }))
199 .unwrap_err();
200 assert_eq!(
201 e,
202 DecodeError::IndexOutOfRange {
203 at: "faceVertexIndices[5]".into(),
204 index: 3,
205 vertex_count: 3
206 }
207 );
208 }
209
210 #[test]
211 fn non_triangle_index_list_is_an_error() {
212 let e = TriangleMesh::from_attribute(&json!({
213 "points": [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]],
214 "faceVertexIndices": [0, 1, 2, 3]
215 }))
216 .unwrap_err();
217 assert_eq!(e, DecodeError::NotTriangles { index_count: 4 });
218 }
219
220 #[test]
221 fn polygon_face_counts_are_rejected() {
222 let e = TriangleMesh::from_attribute(&json!({
223 "points": [[0, 0, 0], [1, 0, 0], [1, 1, 0], [0, 1, 0]],
224 "faceVertexIndices": [0, 1, 2, 3, 0, 2],
225 "faceVertexCounts": [4, 2]
226 }))
227 .unwrap_err();
228 assert_eq!(
229 e,
230 DecodeError::NonTriangleFace {
231 face: 0,
232 vertex_count: 4
233 }
234 );
235 }
236
237 #[test]
238 fn triangle_face_counts_must_cover_indices() {
239 let ok = TriangleMesh::from_attribute(&json!({
240 "points": [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
241 "faceVertexIndices": [0, 1, 2],
242 "faceVertexCounts": [3]
243 }));
244 assert!(ok.is_ok());
245 let e = TriangleMesh::from_attribute(&json!({
246 "points": [[0, 0, 0], [1, 0, 0], [0, 1, 0]],
247 "faceVertexIndices": [0, 1, 2, 0, 1, 2],
248 "faceVertexCounts": [3]
249 }))
250 .unwrap_err();
251 assert_eq!(
252 e,
253 DecodeError::CountMismatch {
254 field: "faceVertexCounts",
255 counted: 3,
256 available: 6
257 }
258 );
259 }
260
261 #[test]
262 fn wrong_shapes_are_errors() {
263 let err = |v: Value| TriangleMesh::from_attribute(&v).unwrap_err();
264 assert_eq!(
265 err(json!({"faceVertexIndices": []})),
266 DecodeError::MissingField { field: "points" }
267 );
268 assert_eq!(
269 err(json!({"points": []})),
270 DecodeError::MissingField {
271 field: "faceVertexIndices"
272 }
273 );
274 assert_eq!(
275 err(json!({"points": [[0, 0]], "faceVertexIndices": []})),
276 DecodeError::WrongShape {
277 at: "points[0]".into(),
278 expected: "array of 3 numbers"
279 }
280 );
281 assert_eq!(
282 err(json!({"points": [[0, 0, 0]], "faceVertexIndices": [0, -1, 0]})),
283 DecodeError::WrongShape {
284 at: "faceVertexIndices[1]".into(),
285 expected: "integer from 0 to 4294967295"
286 }
287 );
288 assert_eq!(
289 err(json!({"points": [[0, 0, 0]], "faceVertexIndices": [0, 0.5, 0]})),
290 DecodeError::WrongShape {
291 at: "faceVertexIndices[1]".into(),
292 expected: "integer from 0 to 4294967295"
293 }
294 );
295 }
296
297 #[test]
298 fn empty_mesh_is_valid() {
299 let mesh =
300 TriangleMesh::from_attribute(&json!({"points": [], "faceVertexIndices": []})).unwrap();
301 assert_eq!(mesh.triangle_count(), 0);
302 }
303}