three_d_asset/geometry/
tri_mesh.rs

1use crate::{prelude::*, Error, Indices, Positions, Result};
2
3///
4/// A CPU-side version of a triangle mesh.
5///
6#[derive(Clone)]
7#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
8pub struct TriMesh {
9    /// The positions of the vertices.
10    /// If there is no indices associated with this mesh, three contiguous positions defines a triangle, in that case, the length must be divisable by 3.
11    pub positions: Positions,
12    /// The indices into the positions, normals, uvs and colors arrays which defines the three vertices of a triangle. Three contiguous indices defines a triangle, therefore the length must be divisable by 3.
13    pub indices: Indices,
14    /// The normals of the vertices.
15    pub normals: Option<Vec<Vec3>>,
16    /// The tangents of the vertices, orthogonal direction to the normal.
17    /// The fourth value specifies the handedness (either -1.0 or 1.0).
18    pub tangents: Option<Vec<Vec4>>,
19    /// The uv coordinates of the vertices.
20    pub uvs: Option<Vec<Vec2>>,
21    /// The colors of the vertices.
22    pub colors: Option<Vec<Srgba>>,
23}
24
25impl std::default::Default for TriMesh {
26    fn default() -> Self {
27        Self {
28            positions: Positions::default(),
29            indices: Indices::None,
30            normals: None,
31            tangents: None,
32            uvs: None,
33            colors: None,
34        }
35    }
36}
37
38impl std::fmt::Debug for TriMesh {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        let mut d = f.debug_struct("Mesh");
41        d.field("positions", &self.positions.len());
42        d.field("indices", &self.indices);
43        d.field("normals", &self.normals.as_ref().map(|v| v.len()));
44        d.field("tangents", &self.tangents.as_ref().map(|v| v.len()));
45        d.field("uvs", &self.uvs.as_ref().map(|v| v.len()));
46        d.field("colors", &self.colors.as_ref().map(|v| v.len()));
47        d.finish()
48    }
49}
50
51impl TriMesh {
52    /// Returns the number of vertices in this mesh.
53    pub fn vertex_count(&self) -> usize {
54        self.positions.len()
55    }
56
57    /// Returns the number of triangles in this mesh.
58    pub fn triangle_count(&self) -> usize {
59        self.indices
60            .len()
61            .map(|i| i / 3)
62            .unwrap_or(self.positions.len() / 3)
63    }
64
65    ///
66    /// Transforms the mesh by the given transformation.
67    ///
68    pub fn transform(&mut self, transform: Mat4) -> Result<()> {
69        match self.positions {
70            Positions::F32(ref mut positions) => {
71                for pos in positions.iter_mut() {
72                    *pos = (transform * pos.extend(1.0)).truncate();
73                }
74            }
75            Positions::F64(ref mut positions) => {
76                let t = transform.cast::<f64>().unwrap();
77                for pos in positions.iter_mut() {
78                    *pos = (t * pos.extend(1.0)).truncate();
79                }
80            }
81        };
82
83        if self.normals.is_some() || self.tangents.is_some() {
84            let normal_transform = transform
85                .invert()
86                .ok_or(Error::FailedInvertingTransformationMatrix)?
87                .transpose();
88
89            if let Some(ref mut normals) = self.normals {
90                for n in normals.iter_mut() {
91                    *n = (normal_transform * n.extend(1.0)).truncate();
92                }
93            }
94            if let Some(ref mut tangents) = self.tangents {
95                for t in tangents.iter_mut() {
96                    *t = (normal_transform * t.truncate().extend(1.0))
97                        .truncate()
98                        .extend(t.w);
99                }
100            }
101        }
102        Ok(())
103    }
104
105    ///
106    /// Returns a square mesh spanning the xy-plane with positions in the range `[-1..1]` in the x and y axes.
107    ///
108    pub fn square() -> Self {
109        let indices = vec![0u8, 1, 2, 2, 3, 0];
110        let halfsize = 1.0;
111        let positions = vec![
112            Vec3::new(-halfsize, -halfsize, 0.0),
113            Vec3::new(halfsize, -halfsize, 0.0),
114            Vec3::new(halfsize, halfsize, 0.0),
115            Vec3::new(-halfsize, halfsize, 0.0),
116        ];
117        let normals = vec![
118            Vec3::new(0.0, 0.0, 1.0),
119            Vec3::new(0.0, 0.0, 1.0),
120            Vec3::new(0.0, 0.0, 1.0),
121            Vec3::new(0.0, 0.0, 1.0),
122        ];
123        let tangents = vec![
124            Vec4::new(1.0, 0.0, 0.0, 1.0),
125            Vec4::new(1.0, 0.0, 0.0, 1.0),
126            Vec4::new(1.0, 0.0, 0.0, 1.0),
127            Vec4::new(1.0, 0.0, 0.0, 1.0),
128        ];
129        let uvs = vec![
130            Vec2::new(0.0, 1.0),
131            Vec2::new(1.0, 1.0),
132            Vec2::new(1.0, 0.0),
133            Vec2::new(0.0, 0.0),
134        ];
135        TriMesh {
136            indices: Indices::U8(indices),
137            positions: Positions::F32(positions),
138            normals: Some(normals),
139            tangents: Some(tangents),
140            uvs: Some(uvs),
141            ..Default::default()
142        }
143    }
144
145    ///
146    /// Returns a circle mesh spanning the xy-plane with radius 1 and center in `(0, 0, 0)`.
147    ///
148    pub fn circle(angle_subdivisions: u32) -> Self {
149        let mut positions = Vec::new();
150        let mut indices = Vec::new();
151        let mut normals = Vec::new();
152        for j in 0..angle_subdivisions {
153            let angle = 2.0 * std::f32::consts::PI * j as f32 / angle_subdivisions as f32;
154
155            positions.push(Vec3::new(angle.cos(), angle.sin(), 0.0));
156            normals.push(Vec3::new(0.0, 0.0, 1.0));
157        }
158
159        for j in 0..angle_subdivisions {
160            indices.push(0);
161            indices.push(j as u16);
162            indices.push(((j + 1) % angle_subdivisions) as u16);
163        }
164        TriMesh {
165            indices: Indices::U16(indices),
166            positions: Positions::F32(positions),
167            normals: Some(normals),
168            ..Default::default()
169        }
170    }
171
172    ///
173    /// Returns a sphere mesh with radius 1 and center in `(0, 0, 0)`.
174    ///
175    pub fn sphere(angle_subdivisions: u32) -> Self {
176        let mut positions = Vec::new();
177        let mut indices = Vec::new();
178        let mut normals = Vec::new();
179
180        positions.push(Vec3::new(0.0, 0.0, 1.0));
181        normals.push(Vec3::new(0.0, 0.0, 1.0));
182
183        for j in 0..angle_subdivisions * 2 {
184            let j1 = (j + 1) % (angle_subdivisions * 2);
185            indices.push(0);
186            indices.push((1 + j) as u16);
187            indices.push((1 + j1) as u16);
188        }
189
190        for i in 0..angle_subdivisions - 1 {
191            let theta = std::f32::consts::PI * (i + 1) as f32 / angle_subdivisions as f32;
192            let sin_theta = theta.sin();
193            let cos_theta = theta.cos();
194            let i0 = 1 + i * angle_subdivisions * 2;
195            let i1 = 1 + (i + 1) * angle_subdivisions * 2;
196
197            for j in 0..angle_subdivisions * 2 {
198                let phi = std::f32::consts::PI * j as f32 / angle_subdivisions as f32;
199                let x = sin_theta * phi.cos();
200                let y = sin_theta * phi.sin();
201                let z = cos_theta;
202                positions.push(Vec3::new(x, y, z));
203                normals.push(Vec3::new(x, y, z));
204
205                if i != angle_subdivisions - 2 {
206                    let j1 = (j + 1) % (angle_subdivisions * 2);
207                    indices.push((i0 + j) as u16);
208                    indices.push((i1 + j1) as u16);
209                    indices.push((i0 + j1) as u16);
210                    indices.push((i1 + j1) as u16);
211                    indices.push((i0 + j) as u16);
212                    indices.push((i1 + j) as u16);
213                }
214            }
215        }
216        positions.push(Vec3::new(0.0, 0.0, -1.0));
217        normals.push(Vec3::new(0.0, 0.0, -1.0));
218
219        let i = 1 + (angle_subdivisions - 2) * angle_subdivisions * 2;
220        for j in 0..angle_subdivisions * 2 {
221            let j1 = (j + 1) % (angle_subdivisions * 2);
222            indices.push((i + j) as u16);
223            indices.push(((angle_subdivisions - 1) * angle_subdivisions * 2 + 1) as u16);
224            indices.push((i + j1) as u16);
225        }
226
227        TriMesh {
228            indices: Indices::U16(indices),
229            positions: Positions::F32(positions),
230            normals: Some(normals),
231            ..Default::default()
232        }
233    }
234
235    ///
236    /// Returns an axis aligned unconnected cube mesh with positions in the range `[-1..1]` in all axes.
237    ///
238    pub fn cube() -> Self {
239        let positions = vec![
240            // Up
241            Vec3::new(1.0, 1.0, -1.0),
242            Vec3::new(-1.0, 1.0, -1.0),
243            Vec3::new(1.0, 1.0, 1.0),
244            Vec3::new(-1.0, 1.0, 1.0),
245            Vec3::new(1.0, 1.0, 1.0),
246            Vec3::new(-1.0, 1.0, -1.0),
247            // Down
248            Vec3::new(-1.0, -1.0, -1.0),
249            Vec3::new(1.0, -1.0, -1.0),
250            Vec3::new(1.0, -1.0, 1.0),
251            Vec3::new(1.0, -1.0, 1.0),
252            Vec3::new(-1.0, -1.0, 1.0),
253            Vec3::new(-1.0, -1.0, -1.0),
254            // Back
255            Vec3::new(1.0, -1.0, -1.0),
256            Vec3::new(-1.0, -1.0, -1.0),
257            Vec3::new(1.0, 1.0, -1.0),
258            Vec3::new(-1.0, 1.0, -1.0),
259            Vec3::new(1.0, 1.0, -1.0),
260            Vec3::new(-1.0, -1.0, -1.0),
261            // Front
262            Vec3::new(-1.0, -1.0, 1.0),
263            Vec3::new(1.0, -1.0, 1.0),
264            Vec3::new(1.0, 1.0, 1.0),
265            Vec3::new(1.0, 1.0, 1.0),
266            Vec3::new(-1.0, 1.0, 1.0),
267            Vec3::new(-1.0, -1.0, 1.0),
268            // Right
269            Vec3::new(1.0, -1.0, -1.0),
270            Vec3::new(1.0, 1.0, -1.0),
271            Vec3::new(1.0, 1.0, 1.0),
272            Vec3::new(1.0, 1.0, 1.0),
273            Vec3::new(1.0, -1.0, 1.0),
274            Vec3::new(1.0, -1.0, -1.0),
275            // Left
276            Vec3::new(-1.0, 1.0, -1.0),
277            Vec3::new(-1.0, -1.0, -1.0),
278            Vec3::new(-1.0, 1.0, 1.0),
279            Vec3::new(-1.0, -1.0, 1.0),
280            Vec3::new(-1.0, 1.0, 1.0),
281            Vec3::new(-1.0, -1.0, -1.0),
282        ];
283        let uvs = vec![
284            // Up
285            Vec2::new(0.25, 0.0),
286            Vec2::new(0.25, 1.0 / 3.0),
287            Vec2::new(0.5, 0.0),
288            Vec2::new(0.5, 1.0 / 3.0),
289            Vec2::new(0.5, 0.0),
290            Vec2::new(0.25, 1.0 / 3.0),
291            // Down
292            Vec2::new(0.25, 2.0 / 3.0),
293            Vec2::new(0.25, 1.0),
294            Vec2::new(0.5, 1.0),
295            Vec2::new(0.5, 1.0),
296            Vec2::new(0.5, 2.0 / 3.0),
297            Vec2::new(0.25, 2.0 / 3.0),
298            // Back
299            Vec2::new(0.0, 2.0 / 3.0),
300            Vec2::new(0.25, 2.0 / 3.0),
301            Vec2::new(0.0, 1.0 / 3.0),
302            Vec2::new(0.25, 1.0 / 3.0),
303            Vec2::new(0.0, 1.0 / 3.0),
304            Vec2::new(0.25, 2.0 / 3.0),
305            // Front
306            Vec2::new(0.5, 2.0 / 3.0),
307            Vec2::new(0.75, 2.0 / 3.0),
308            Vec2::new(0.75, 1.0 / 3.0),
309            Vec2::new(0.75, 1.0 / 3.0),
310            Vec2::new(0.5, 1.0 / 3.0),
311            Vec2::new(0.5, 2.0 / 3.0),
312            // Right
313            Vec2::new(1.0, 2.0 / 3.0),
314            Vec2::new(1.0, 1.0 / 3.0),
315            Vec2::new(0.75, 1.0 / 3.0),
316            Vec2::new(0.75, 1.0 / 3.0),
317            Vec2::new(0.75, 2.0 / 3.0),
318            Vec2::new(1.0, 2.0 / 3.0),
319            // Left
320            Vec2::new(0.25, 1.0 / 3.0),
321            Vec2::new(0.25, 2.0 / 3.0),
322            Vec2::new(0.5, 1.0 / 3.0),
323            Vec2::new(0.5, 2.0 / 3.0),
324            Vec2::new(0.5, 1.0 / 3.0),
325            Vec2::new(0.25, 2.0 / 3.0),
326        ];
327        let mut mesh = TriMesh {
328            positions: Positions::F32(positions),
329            uvs: Some(uvs),
330            ..Default::default()
331        };
332        mesh.compute_normals();
333        mesh.compute_tangents();
334        mesh
335    }
336
337    ///
338    /// Returns a cylinder mesh around the x-axis in the range `[0..1]` and with radius 1.
339    ///
340    pub fn cylinder(angle_subdivisions: u32) -> Self {
341        let length_subdivisions = 1;
342        let mut positions = Vec::new();
343        let mut indices = Vec::new();
344        for i in 0..length_subdivisions + 1 {
345            let x = i as f32 / length_subdivisions as f32;
346            for j in 0..angle_subdivisions {
347                let angle = 2.0 * std::f32::consts::PI * j as f32 / angle_subdivisions as f32;
348
349                positions.push(Vec3::new(x, angle.cos(), angle.sin()));
350            }
351        }
352        for i in 0..length_subdivisions {
353            for j in 0..angle_subdivisions {
354                indices.push((i * angle_subdivisions + j) as u16);
355                indices.push((i * angle_subdivisions + (j + 1) % angle_subdivisions) as u16);
356                indices.push(((i + 1) * angle_subdivisions + (j + 1) % angle_subdivisions) as u16);
357
358                indices.push((i * angle_subdivisions + j) as u16);
359                indices.push(((i + 1) * angle_subdivisions + (j + 1) % angle_subdivisions) as u16);
360                indices.push(((i + 1) * angle_subdivisions + j) as u16);
361            }
362        }
363        let mut mesh = Self {
364            positions: Positions::F32(positions),
365            indices: Indices::U16(indices),
366            ..Default::default()
367        };
368        mesh.compute_normals();
369        mesh
370    }
371
372    ///
373    /// Returns a cone mesh around the x-axis in the range `[0..1]` and with radius 1 at -1.0.
374    ///
375    pub fn cone(angle_subdivisions: u32) -> Self {
376        let length_subdivisions = 1;
377        let mut positions = Vec::new();
378        let mut indices = Vec::new();
379        for i in 0..length_subdivisions + 1 {
380            let x = i as f32 / length_subdivisions as f32;
381            for j in 0..angle_subdivisions {
382                let angle = 2.0 * std::f32::consts::PI * j as f32 / angle_subdivisions as f32;
383
384                positions.push(Vec3::new(
385                    x,
386                    angle.cos() * (1.0 - x),
387                    angle.sin() * (1.0 - x),
388                ));
389            }
390        }
391        for i in 0..length_subdivisions {
392            for j in 0..angle_subdivisions {
393                indices.push((i * angle_subdivisions + j) as u16);
394                indices.push((i * angle_subdivisions + (j + 1) % angle_subdivisions) as u16);
395                indices.push(((i + 1) * angle_subdivisions + (j + 1) % angle_subdivisions) as u16);
396
397                indices.push((i * angle_subdivisions + j) as u16);
398                indices.push(((i + 1) * angle_subdivisions + (j + 1) % angle_subdivisions) as u16);
399                indices.push(((i + 1) * angle_subdivisions + j) as u16);
400            }
401        }
402        let mut mesh = Self {
403            positions: Positions::F32(positions),
404            indices: Indices::U16(indices),
405            ..Default::default()
406        };
407        mesh.compute_normals();
408        mesh
409    }
410
411    ///
412    /// Returns an arrow mesh around the x-axis in the range `[0..1]` and with radius 1.
413    /// The tail length and radius should be in the range `]0..1[`.
414    ///
415    pub fn arrow(tail_length: f32, tail_radius: f32, angle_subdivisions: u32) -> Self {
416        let mut arrow = Self::cylinder(angle_subdivisions);
417        arrow
418            .transform(Mat4::from_nonuniform_scale(
419                tail_length,
420                tail_radius,
421                tail_radius,
422            ))
423            .unwrap();
424        let mut cone = Self::cone(angle_subdivisions);
425        cone.transform(
426            Mat4::from_translation(Vec3::new(tail_length, 0.0, 0.0))
427                * Mat4::from_nonuniform_scale(1.0 - tail_length, 1.0, 1.0),
428        )
429        .unwrap();
430        let mut indices = arrow.indices.into_u32().unwrap();
431        let cone_indices = cone.indices.into_u32().unwrap();
432        let offset = indices.iter().max().unwrap() + 1;
433        indices.extend(cone_indices.iter().map(|i| i + offset));
434        arrow.indices = Indices::U16(indices.iter().map(|i| *i as u16).collect());
435
436        if let Positions::F32(ref mut p) = arrow.positions {
437            if let Positions::F32(ref p2) = cone.positions {
438                p.extend(p2);
439            }
440        }
441        arrow
442            .normals
443            .as_mut()
444            .unwrap()
445            .extend(cone.normals.as_ref().unwrap());
446        arrow
447    }
448
449    ///
450    /// Computes the per vertex normals and updates the normals of the mesh.
451    /// It will override the current normals if they already exist.
452    ///
453    pub fn compute_normals(&mut self) {
454        let mut normals = vec![Vec3::new(0.0, 0.0, 0.0); self.positions.len()];
455        self.for_each_triangle(|i0, i1, i2| {
456            let normal = match self.positions {
457                Positions::F32(ref positions) => {
458                    let p0 = positions[i0];
459                    let p1 = positions[i1];
460                    let p2 = positions[i2];
461                    (p1 - p0).cross(p2 - p0)
462                }
463                Positions::F64(ref positions) => {
464                    let p0 = positions[i0];
465                    let p1 = positions[i1];
466                    let p2 = positions[i2];
467                    let n = (p1 - p0).cross(p2 - p0);
468                    Vec3::new(n.x as f32, n.y as f32, n.z as f32)
469                }
470            };
471            normals[i0] += normal;
472            normals[i1] += normal;
473            normals[i2] += normal;
474        });
475
476        for n in normals.iter_mut() {
477            *n = n.normalize();
478        }
479        self.normals = Some(normals);
480    }
481
482    ///
483    /// Computes the per vertex tangents and updates the tangents of the mesh.
484    /// It will override the current tangents if they already exist.
485    ///
486    pub fn compute_tangents(&mut self) {
487        if self.normals.is_none() || self.uvs.is_none() {
488            panic!("mesh must have both normals and uv coordinates to be able to compute tangents");
489        }
490        let mut tan1 = vec![Vec3::new(0.0, 0.0, 0.0); self.positions.len()];
491        let mut tan2 = vec![Vec3::new(0.0, 0.0, 0.0); self.positions.len()];
492
493        self.for_each_triangle(|i0, i1, i2| {
494            let (a, b, c) = match self.positions {
495                Positions::F32(ref positions) => (positions[i0], positions[i1], positions[i2]),
496                Positions::F64(ref positions) => {
497                    let (a, b, c) = (positions[i0], positions[i1], positions[i2]);
498                    (
499                        Vec3::new(a.x as f32, a.y as f32, a.z as f32),
500                        Vec3::new(b.x as f32, b.y as f32, b.z as f32),
501                        Vec3::new(c.x as f32, c.y as f32, c.z as f32),
502                    )
503                }
504            };
505            let uva = self.uvs.as_ref().unwrap()[i0];
506            let uvb = self.uvs.as_ref().unwrap()[i1];
507            let uvc = self.uvs.as_ref().unwrap()[i2];
508
509            let ba = b - a;
510            let ca = c - a;
511
512            let uvba = uvb - uva;
513            let uvca = uvc - uva;
514
515            let d = uvba.x * uvca.y - uvca.x * uvba.y;
516            if d.abs() > 0.00001 {
517                let r = 1.0 / d;
518                let sdir = (ba * uvca.y - ca * uvba.y) * r;
519                let tdir = (ca * uvba.x - ba * uvca.x) * r;
520                tan1[i0] += sdir;
521                tan1[i1] += sdir;
522                tan1[i2] += sdir;
523                tan2[i0] += tdir;
524                tan2[i1] += tdir;
525                tan2[i2] += tdir;
526            }
527        });
528
529        let mut tangents = vec![Vec4::new(0.0, 0.0, 0.0, 0.0); self.positions.len()];
530        self.for_each_vertex(|index| {
531            let normal = self.normals.as_ref().unwrap()[index];
532            let t = tan1[index];
533            let tangent = (t - normal * normal.dot(t)).normalize();
534            let handedness = if normal.cross(tangent).dot(tan2[index]) < 0.0 {
535                1.0
536            } else {
537                -1.0
538            };
539            tangents[index] = tangent.extend(handedness);
540        });
541
542        self.tangents = Some(tangents);
543    }
544
545    ///
546    ///  Iterates over all vertices in this mesh and calls the callback function with the index for each vertex.
547    ///
548    pub fn for_each_vertex(&self, mut callback: impl FnMut(usize)) {
549        for i in 0..self.positions.len() {
550            callback(i);
551        }
552    }
553
554    ///
555    /// Iterates over all triangles in this mesh and calls the callback function with the three indices, one for each vertex in the triangle.
556    ///
557    pub fn for_each_triangle(&self, mut callback: impl FnMut(usize, usize, usize)) {
558        match self.indices {
559            Indices::U8(ref indices) => {
560                for face in 0..indices.len() / 3 {
561                    let index0 = indices[face * 3] as usize;
562                    let index1 = indices[face * 3 + 1] as usize;
563                    let index2 = indices[face * 3 + 2] as usize;
564                    callback(index0, index1, index2);
565                }
566            }
567            Indices::U16(ref indices) => {
568                for face in 0..indices.len() / 3 {
569                    let index0 = indices[face * 3] as usize;
570                    let index1 = indices[face * 3 + 1] as usize;
571                    let index2 = indices[face * 3 + 2] as usize;
572                    callback(index0, index1, index2);
573                }
574            }
575            Indices::U32(ref indices) => {
576                for face in 0..indices.len() / 3 {
577                    let index0 = indices[face * 3] as usize;
578                    let index1 = indices[face * 3 + 1] as usize;
579                    let index2 = indices[face * 3 + 2] as usize;
580                    callback(index0, index1, index2);
581                }
582            }
583            Indices::None => {
584                for face in 0..self.triangle_count() {
585                    callback(face * 3, face * 3 + 1, face * 3 + 2);
586                }
587            }
588        }
589    }
590
591    ///
592    /// Computes the [AxisAlignedBoundingBox] for this triangle mesh.
593    ///
594    pub fn compute_aabb(&self) -> AxisAlignedBoundingBox {
595        self.positions.compute_aabb()
596    }
597
598    ///
599    /// Returns an error if the mesh is not valid.
600    ///
601    pub fn validate(&self) -> Result<()> {
602        if self.indices.len().map(|i| i % 3 != 0).unwrap_or(false) {
603            Err(Error::InvalidNumberOfIndices(self.indices.len().unwrap()))?;
604        }
605        let vertex_count = self.vertex_count();
606        let max_index = match &self.indices {
607            Indices::U8(ind) => ind.iter().max().map(|m| *m as usize),
608            Indices::U16(ind) => ind.iter().max().map(|m| *m as usize),
609            Indices::U32(ind) => ind.iter().max().map(|m| *m as usize),
610            Indices::None => None,
611        };
612        if max_index.map(|i| i >= vertex_count).unwrap_or(false) {
613            Err(Error::InvalidIndices(max_index.unwrap(), vertex_count))?;
614        }
615        let buffer_check = |length: Option<usize>, name: &str| -> Result<()> {
616            if let Some(length) = length {
617                if length < vertex_count {
618                    Err(Error::InvalidBufferLength(
619                        name.to_string(),
620                        vertex_count,
621                        length,
622                    ))?;
623                }
624            }
625            Ok(())
626        };
627
628        buffer_check(Some(self.positions.len()), "position")?;
629        buffer_check(self.normals.as_ref().map(|b| b.len()), "normal")?;
630        buffer_check(self.tangents.as_ref().map(|b| b.len()), "tangent")?;
631        buffer_check(self.colors.as_ref().map(|b| b.len()), "color")?;
632        buffer_check(self.uvs.as_ref().map(|b| b.len()), "uv coordinate")?;
633
634        Ok(())
635    }
636}