1#[cfg(all(feature = "dim3", feature = "async-collider"))]
2use {
3 bevy::mesh::{Indices, VertexAttributeValues},
4 bevy::prelude::*,
5};
6
7use rapier::{
8 parry::transformation::voxelization::FillMode,
9 prelude::{FeatureId, Ray, SharedShape, Vector, Voxels, DIM},
10};
11
12use super::{get_snapped_scale, shape_views::*};
13#[cfg(all(feature = "dim3", feature = "async-collider"))]
14use crate::geometry::ComputedColliderShape;
15use crate::math::{Real, Rot, Vect};
16use crate::{
17 geometry::{Collider, PointProjection, RayIntersection, TriMeshFlags, VHACDParameters},
18 math::IVect,
19};
20
21impl Collider {
22 pub fn scale(&self) -> Vect {
24 self.scale
25 }
26
27 pub fn promote_scaled_shape(&mut self) {
30 self.unscaled = self.raw.clone();
31 self.scale = Vect::ONE;
32 }
33
34 pub fn compound(shapes: Vec<(Vect, Rot, Collider)>) -> Self {
36 let shapes = shapes
37 .into_iter()
38 .map(|(t, r, s)| (crate::utils::pose_from(t, r), s.raw))
39 .collect();
40 SharedShape::compound(shapes).into()
41 }
42
43 pub fn ball(radius: Real) -> Self {
45 SharedShape::ball(radius).into()
46 }
47
48 pub fn halfspace(outward_normal: Vect) -> Option<Self> {
51 let normal = Vector::from(outward_normal);
52 if normal.length_squared() < 1.0e-12 {
53 return None;
54 }
55 Some(SharedShape::halfspace(normal.normalize()).into())
56 }
57
58 #[cfg(feature = "dim3")]
61 pub fn cylinder(half_height: Real, radius: Real) -> Self {
62 SharedShape::cylinder(half_height, radius).into()
63 }
64
65 #[cfg(feature = "dim3")]
69 pub fn round_cylinder(half_height: Real, radius: Real, border_radius: Real) -> Self {
70 SharedShape::round_cylinder(half_height, radius, border_radius).into()
71 }
72
73 #[cfg(feature = "dim3")]
76 pub fn cone(half_height: Real, radius: Real) -> Self {
77 SharedShape::cone(half_height, radius).into()
78 }
79
80 #[cfg(feature = "dim3")]
84 pub fn round_cone(half_height: Real, radius: Real, border_radius: Real) -> Self {
85 SharedShape::round_cone(half_height, radius, border_radius).into()
86 }
87
88 #[cfg(feature = "dim2")]
90 pub fn cuboid(half_x: Real, half_y: Real) -> Self {
91 SharedShape::cuboid(half_x, half_y).into()
92 }
93
94 #[cfg(feature = "dim2")]
97 pub fn round_cuboid(half_x: Real, half_y: Real, border_radius: Real) -> Self {
98 SharedShape::round_cuboid(half_x, half_y, border_radius).into()
99 }
100
101 pub fn capsule(start: Vect, end: Vect, radius: Real) -> Self {
103 SharedShape::capsule(start, end, radius).into()
104 }
105
106 pub fn capsule_x(half_height: Real, radius: Real) -> Self {
108 let p = Vector::X * half_height;
109 SharedShape::capsule(-p, p, radius).into()
110 }
111
112 pub fn capsule_y(half_height: Real, radius: Real) -> Self {
114 let p = Vector::Y * half_height;
115 SharedShape::capsule(-p, p, radius).into()
116 }
117
118 #[cfg(feature = "dim3")]
120 pub fn capsule_z(half_height: Real, radius: Real) -> Self {
121 let p = Vector::Z * half_height;
122 SharedShape::capsule(-p, p, radius).into()
123 }
124
125 #[cfg(feature = "dim3")]
127 pub fn cuboid(hx: Real, hy: Real, hz: Real) -> Self {
128 SharedShape::cuboid(hx, hy, hz).into()
129 }
130
131 #[cfg(feature = "dim3")]
134 pub fn round_cuboid(half_x: Real, half_y: Real, half_z: Real, border_radius: Real) -> Self {
135 SharedShape::round_cuboid(half_x, half_y, half_z, border_radius).into()
136 }
137
138 pub fn segment(a: Vect, b: Vect) -> Self {
140 SharedShape::segment(a, b).into()
141 }
142
143 pub fn triangle(a: Vect, b: Vect, c: Vect) -> Self {
145 SharedShape::triangle(a, b, c).into()
146 }
147
148 pub fn round_triangle(a: Vect, b: Vect, c: Vect, border_radius: Real) -> Self {
150 SharedShape::round_triangle(a, b, c, border_radius).into()
151 }
152
153 fn ivec_array_from_point_int_array(points: &[IVect]) -> Vec<IVect> {
154 points.to_vec()
155 }
156
157 fn vec_array_from_point_float_array(points: &[Vect]) -> Vec<Vect> {
158 points.to_vec()
159 }
160
161 pub fn voxels(voxel_size: Vect, grid_coordinates: &[IVect]) -> Self {
171 let shape = Voxels::new(
172 voxel_size,
173 &Self::ivec_array_from_point_int_array(grid_coordinates),
174 );
175 SharedShape::new(shape).into()
176 }
177
178 pub fn voxels_from_points(voxel_size: Vect, points: &[Vect]) -> Self {
183 SharedShape::voxels_from_points(voxel_size, &Self::vec_array_from_point_float_array(points))
184 .into()
185 }
186
187 pub fn voxelized_mesh(
190 vertices: &[Vect],
191 indices: &[[u32; DIM]],
192 voxel_size: Real,
193 fill_mode: FillMode,
194 ) -> Self {
195 let vertices = Self::vec_array_from_point_float_array(vertices);
196 SharedShape::voxelized_mesh(&vertices, indices, voxel_size, fill_mode).into()
197 }
198
199 pub fn voxelized_convex_decomposition(vertices: &[Vect], indices: &[[u32; DIM]]) -> Vec<Self> {
202 Self::voxelized_convex_decomposition_with_params(
203 vertices,
204 indices,
205 &VHACDParameters::default(),
206 )
207 }
208
209 pub fn voxelized_convex_decomposition_with_params(
212 vertices: &[Vect],
213 indices: &[[u32; DIM]],
214 params: &VHACDParameters,
215 ) -> Vec<Self> {
216 SharedShape::voxelized_convex_decomposition_with_params(
217 &Self::vec_array_from_point_float_array(vertices),
218 indices,
219 params,
220 )
221 .into_iter()
222 .map(|c| c.into())
223 .collect()
224 }
225
226 pub fn polyline(vertices: Vec<Vect>, indices: Option<Vec<[u32; 2]>>) -> Self {
228 let vertices = vertices.into_iter().collect();
229 SharedShape::polyline(vertices, indices).into()
230 }
231
232 pub fn trimesh(
234 vertices: Vec<Vect>,
235 indices: Vec<[u32; 3]>,
236 ) -> Result<Self, crate::rapier::prelude::TriMeshBuilderError> {
237 let vertices = vertices.into_iter().collect();
238 Ok(SharedShape::trimesh(vertices, indices)?.into())
239 }
240
241 pub fn trimesh_with_flags(
244 vertices: Vec<Vect>,
245 indices: Vec<[u32; 3]>,
246 flags: TriMeshFlags,
247 ) -> Result<Self, crate::rapier::prelude::TriMeshBuilderError> {
248 let vertices = vertices.into_iter().collect();
249 Ok(SharedShape::trimesh_with_flags(vertices, indices, flags)?.into())
250 }
251
252 #[cfg(all(feature = "dim3", feature = "async-collider"))]
256 pub fn from_bevy_mesh(mesh: &Mesh, collider_shape: &ComputedColliderShape) -> Option<Self> {
257 let (vtx, idx) = extract_mesh_vertices_indices(mesh)?;
258
259 match collider_shape {
260 ComputedColliderShape::TriMesh(flags) => Some(
261 SharedShape::trimesh_with_flags(vtx, idx, *flags)
262 .ok()?
263 .into(),
264 ),
265 ComputedColliderShape::ConvexHull => {
266 SharedShape::convex_hull(&vtx).map(|shape| shape.into())
267 }
268 ComputedColliderShape::ConvexDecomposition(params) => {
269 Some(SharedShape::convex_decomposition_with_params(&vtx, &idx, params).into())
270 }
271 }
272 }
273
274 pub fn convex_decomposition(vertices: &[Vect], indices: &[[u32; DIM]]) -> Self {
277 let vertices: Vec<_> = vertices.to_vec();
278 SharedShape::convex_decomposition(&vertices, indices).into()
279 }
280
281 pub fn round_convex_decomposition(
284 vertices: &[Vect],
285 indices: &[[u32; DIM]],
286 border_radius: Real,
287 ) -> Self {
288 let vertices: Vec<_> = vertices.to_vec();
289 SharedShape::round_convex_decomposition(&vertices, indices, border_radius).into()
290 }
291
292 pub fn convex_decomposition_with_params(
295 vertices: &[Vect],
296 indices: &[[u32; DIM]],
297 params: &VHACDParameters,
298 ) -> Self {
299 let vertices: Vec<_> = vertices.to_vec();
300 SharedShape::convex_decomposition_with_params(&vertices, indices, params).into()
301 }
302
303 pub fn round_convex_decomposition_with_params(
306 vertices: &[Vect],
307 indices: &[[u32; DIM]],
308 params: &VHACDParameters,
309 border_radius: Real,
310 ) -> Self {
311 let vertices: Vec<_> = vertices.to_vec();
312 SharedShape::round_convex_decomposition_with_params(
313 &vertices,
314 indices,
315 params,
316 border_radius,
317 )
318 .into()
319 }
320
321 pub fn convex_hull(points: &[Vect]) -> Option<Self> {
324 let points: Vec<_> = points.to_vec();
325 SharedShape::convex_hull(&points).map(Into::into)
326 }
327
328 pub fn round_convex_hull(points: &[Vect], border_radius: Real) -> Option<Self> {
332 let points: Vec<_> = points.to_vec();
333 SharedShape::round_convex_hull(&points, border_radius).map(Into::into)
334 }
335
336 #[cfg(feature = "dim2")]
340 pub fn convex_polyline(points: Vec<Vect>) -> Option<Self> {
341 SharedShape::convex_polyline(points).map(Into::into)
342 }
343
344 #[cfg(feature = "dim2")]
348 pub fn round_convex_polyline(points: Vec<Vect>, border_radius: Real) -> Option<Self> {
349 SharedShape::round_convex_polyline(points, border_radius).map(Into::into)
350 }
351
352 #[cfg(feature = "dim3")]
356 pub fn convex_mesh(points: Vec<Vect>, indices: &[[u32; 3]]) -> Option<Self> {
357 let points = points.into_iter().collect();
358 SharedShape::convex_mesh(points, indices).map(Into::into)
359 }
360
361 #[cfg(feature = "dim3")]
365 pub fn round_convex_mesh(
366 points: Vec<Vect>,
367 indices: &[[u32; 3]],
368 border_radius: Real,
369 ) -> Option<Self> {
370 let points = points.into_iter().collect();
371 SharedShape::round_convex_mesh(points, indices, border_radius).map(Into::into)
372 }
373
374 #[cfg(feature = "dim2")]
377 pub fn heightfield(heights: Vec<Real>, scale: Vect) -> Self {
378 SharedShape::heightfield(heights, scale).into()
379 }
380
381 #[cfg(feature = "dim3")]
384 pub fn heightfield(heights: Vec<Real>, num_rows: usize, num_cols: usize, scale: Vect) -> Self {
385 assert_eq!(
386 heights.len(),
387 num_rows * num_cols,
388 "Invalid number of heights provided."
389 );
390 let heights = rapier::parry::utils::Array2::new(num_rows, num_cols, heights);
391 SharedShape::heightfield(heights, scale).into()
392 }
393
394 pub fn as_typed_shape(&self) -> ColliderView<'_> {
396 self.raw.as_typed_shape().into()
397 }
398
399 pub fn as_unscaled_typed_shape(&self) -> ColliderView<'_> {
401 self.unscaled.as_typed_shape().into()
402 }
403
404 pub fn as_ball(&self) -> Option<BallView<'_>> {
406 self.raw.as_ball().map(|s| BallView { raw: s })
407 }
408
409 pub fn as_cuboid(&self) -> Option<CuboidView<'_>> {
411 self.raw.as_cuboid().map(|s| CuboidView { raw: s })
412 }
413
414 pub fn as_capsule(&self) -> Option<CapsuleView<'_>> {
416 self.raw.as_capsule().map(|s| CapsuleView { raw: s })
417 }
418
419 pub fn as_segment(&self) -> Option<SegmentView<'_>> {
421 self.raw.as_segment().map(|s| SegmentView { raw: s })
422 }
423
424 pub fn as_triangle(&self) -> Option<TriangleView<'_>> {
426 self.raw.as_triangle().map(|s| TriangleView { raw: s })
427 }
428
429 pub fn as_voxels(&self) -> Option<VoxelsView<'_>> {
431 self.raw.as_voxels().map(|s| VoxelsView { raw: s })
432 }
433
434 pub fn as_trimesh(&self) -> Option<TriMeshView<'_>> {
436 self.raw.as_trimesh().map(|s| TriMeshView { raw: s })
437 }
438
439 pub fn as_polyline(&self) -> Option<PolylineView<'_>> {
441 self.raw.as_polyline().map(|s| PolylineView { raw: s })
442 }
443
444 pub fn as_halfspace(&self) -> Option<HalfSpaceView<'_>> {
446 self.raw.as_halfspace().map(|s| HalfSpaceView { raw: s })
447 }
448
449 pub fn as_heightfield(&self) -> Option<HeightFieldView<'_>> {
451 self.raw
452 .as_heightfield()
453 .map(|s| HeightFieldView { raw: s })
454 }
455
456 pub fn as_compound(&self) -> Option<CompoundView<'_>> {
458 self.raw.as_compound().map(|s| CompoundView { raw: s })
459 }
460
461 #[cfg(feature = "dim2")]
463 pub fn as_convex_polygon(&self) -> Option<ConvexPolygonView<'_>> {
464 self.raw
465 .as_convex_polygon()
466 .map(|s| ConvexPolygonView { raw: s })
467 }
468
469 #[cfg(feature = "dim3")]
471 pub fn as_convex_polyhedron(&self) -> Option<ConvexPolyhedronView<'_>> {
472 self.raw
473 .as_convex_polyhedron()
474 .map(|s| ConvexPolyhedronView { raw: s })
475 }
476
477 #[cfg(feature = "dim3")]
479 pub fn as_cylinder(&self) -> Option<CylinderView<'_>> {
480 self.raw.as_cylinder().map(|s| CylinderView { raw: s })
481 }
482
483 #[cfg(feature = "dim3")]
485 pub fn as_cone(&self) -> Option<ConeView<'_>> {
486 self.raw.as_cone().map(|s| ConeView { raw: s })
487 }
488
489 pub fn as_ball_mut(&mut self) -> Option<BallViewMut<'_>> {
491 self.raw
492 .make_mut()
493 .as_ball_mut()
494 .map(|s| BallViewMut { raw: s })
495 }
496
497 pub fn as_cuboid_mut(&mut self) -> Option<CuboidViewMut<'_>> {
499 self.raw
500 .make_mut()
501 .as_cuboid_mut()
502 .map(|s| CuboidViewMut { raw: s })
503 }
504
505 pub fn as_capsule_mut(&mut self) -> Option<CapsuleViewMut<'_>> {
507 self.raw
508 .make_mut()
509 .as_capsule_mut()
510 .map(|s| CapsuleViewMut { raw: s })
511 }
512
513 pub fn as_segment_mut(&mut self) -> Option<SegmentViewMut<'_>> {
515 self.raw
516 .make_mut()
517 .as_segment_mut()
518 .map(|s| SegmentViewMut { raw: s })
519 }
520
521 pub fn as_triangle_mut(&mut self) -> Option<TriangleViewMut<'_>> {
523 self.raw
524 .make_mut()
525 .as_triangle_mut()
526 .map(|s| TriangleViewMut { raw: s })
527 }
528
529 pub fn as_voxels_mut(&mut self) -> Option<VoxelsViewMut<'_>> {
531 self.raw
532 .make_mut()
533 .as_voxels_mut()
534 .map(|s| VoxelsViewMut { raw: s })
535 }
536
537 pub fn as_trimesh_mut(&mut self) -> Option<TriMeshViewMut<'_>> {
539 self.raw
540 .make_mut()
541 .as_trimesh_mut()
542 .map(|s| TriMeshViewMut { raw: s })
543 }
544
545 pub fn as_polyline_mut(&mut self) -> Option<PolylineViewMut<'_>> {
547 self.raw
548 .make_mut()
549 .as_polyline_mut()
550 .map(|s| PolylineViewMut { raw: s })
551 }
552
553 pub fn as_halfspace_mut(&mut self) -> Option<HalfSpaceViewMut<'_>> {
555 self.raw
556 .make_mut()
557 .as_halfspace_mut()
558 .map(|s| HalfSpaceViewMut { raw: s })
559 }
560
561 pub fn as_heightfield_mut(&mut self) -> Option<HeightFieldViewMut<'_>> {
563 self.raw
564 .make_mut()
565 .as_heightfield_mut()
566 .map(|s| HeightFieldViewMut { raw: s })
567 }
568
569 #[cfg(feature = "dim3")]
594 pub fn as_cylinder_mut(&mut self) -> Option<CylinderViewMut<'_>> {
595 self.raw
596 .make_mut()
597 .as_cylinder_mut()
598 .map(|s| CylinderViewMut { raw: s })
599 }
600
601 #[cfg(feature = "dim3")]
603 pub fn as_cone_mut(&mut self) -> Option<ConeViewMut<'_>> {
604 self.raw
605 .make_mut()
606 .as_cone_mut()
607 .map(|s| ConeViewMut { raw: s })
608 }
609
610 pub fn set_scale(&mut self, scale: Vect, num_subdivisions: u32) {
618 let scale = get_snapped_scale(scale);
619
620 if scale == self.scale {
621 return;
623 }
624
625 if scale == Vect::ONE {
626 self.raw = self.unscaled.clone();
628 self.scale = Vect::ONE;
629 return;
630 }
631
632 if let Some(scaled) = self
633 .as_unscaled_typed_shape()
634 .raw_scale_by(scale, num_subdivisions)
635 {
636 self.raw = scaled;
637 self.scale = scale;
638 } else {
639 log::error!("Failed to create the scaled convex hull geometry.");
640 }
641 }
642
643 pub fn project_local_point_with_max_dist(
647 &self,
648 point: Vect,
649 solid: bool,
650 max_dist: Real,
651 ) -> Option<PointProjection> {
652 self.raw
653 .project_local_point_with_max_dist(point, solid, max_dist)
654 .map(Into::into)
655 }
656
657 pub fn project_point_with_max_dist(
659 &self,
660 translation: Vect,
661 rotation: Rot,
662 point: Vect,
663 solid: bool,
664 max_dist: Real,
665 ) -> Option<PointProjection> {
666 let pos = crate::utils::pose_from(translation, rotation);
667 self.raw
668 .project_point_with_max_dist(&pos, point, solid, max_dist)
669 .map(Into::into)
670 }
671
672 pub fn project_local_point(&self, point: Vect, solid: bool) -> PointProjection {
676 self.raw.project_local_point(point, solid).into()
677 }
678
679 pub fn project_local_point_and_get_feature(&self, point: Vect) -> (PointProjection, FeatureId) {
682 let (proj, feat) = self.raw.project_local_point_and_get_feature(point);
683 (proj.into(), feat)
684 }
685
686 pub fn distance_to_local_point(&self, point: Vect, solid: bool) -> Real {
688 self.raw.distance_to_local_point(point, solid)
689 }
690
691 pub fn contains_local_point(&self, point: Vect) -> bool {
693 self.raw.contains_local_point(point)
694 }
695
696 pub fn project_point(
698 &self,
699 translation: Vect,
700 rotation: Rot,
701 point: Vect,
702 solid: bool,
703 ) -> PointProjection {
704 let pos = crate::utils::pose_from(translation, rotation);
705 self.raw.project_point(&pos, point, solid).into()
706 }
707
708 #[inline]
710 pub fn distance_to_point(
711 &self,
712 translation: Vect,
713 rotation: Rot,
714 point: Vect,
715 solid: bool,
716 ) -> Real {
717 let pos = crate::utils::pose_from(translation, rotation);
718 self.raw.distance_to_point(&pos, point, solid)
719 }
720
721 pub fn project_point_and_get_feature(
724 &self,
725 translation: Vect,
726 rotation: Rot,
727 point: Vect,
728 ) -> (PointProjection, FeatureId) {
729 let pos = crate::utils::pose_from(translation, rotation);
730 let (proj, feat) = self.raw.project_point_and_get_feature(&pos, point);
731 (proj.into(), feat)
732 }
733
734 pub fn contains_point(&self, translation: Vect, rotation: Rot, point: Vect) -> bool {
736 let pos = crate::utils::pose_from(translation, rotation);
737 self.raw.contains_point(&pos, point)
738 }
739
740 pub fn cast_local_ray(
742 &self,
743 ray_origin: Vect,
744 ray_dir: Vect,
745 max_time_of_impact: Real,
746 solid: bool,
747 ) -> Option<Real> {
748 let ray = Ray::new(ray_origin, ray_dir);
749 self.raw.cast_local_ray(&ray, max_time_of_impact, solid)
750 }
751
752 pub fn cast_local_ray_and_get_normal(
754 &self,
755 ray_origin: Vect,
756 ray_dir: Vect,
757 max_time_of_impact: Real,
758 solid: bool,
759 ) -> Option<RayIntersection> {
760 let ray = Ray::new(ray_origin, ray_dir);
761 self.raw
762 .cast_local_ray_and_get_normal(&ray, max_time_of_impact, solid)
763 .map(|inter| RayIntersection::from_rapier(inter, ray_origin, ray_dir))
764 }
765
766 pub fn intersects_local_ray(
768 &self,
769 ray_origin: Vect,
770 ray_dir: Vect,
771 max_time_of_impact: Real,
772 ) -> bool {
773 let ray = Ray::new(ray_origin, ray_dir);
774 self.raw.intersects_local_ray(&ray, max_time_of_impact)
775 }
776
777 pub fn cast_ray(
779 &self,
780 translation: Vect,
781 rotation: Rot,
782 ray_origin: Vect,
783 ray_dir: Vect,
784 max_time_of_impact: Real,
785 solid: bool,
786 ) -> Option<Real> {
787 let pos = crate::utils::pose_from(translation, rotation);
788 let ray = Ray::new(ray_origin, ray_dir);
789 self.raw.cast_ray(&pos, &ray, max_time_of_impact, solid)
790 }
791
792 pub fn cast_ray_and_get_normal(
794 &self,
795 translation: Vect,
796 rotation: Rot,
797 ray_origin: Vect,
798 ray_dir: Vect,
799 max_time_of_impact: Real,
800 solid: bool,
801 ) -> Option<RayIntersection> {
802 let pos = crate::utils::pose_from(translation, rotation);
803 let ray = Ray::new(ray_origin, ray_dir);
804 self.raw
805 .cast_ray_and_get_normal(&pos, &ray, max_time_of_impact, solid)
806 .map(|inter| RayIntersection::from_rapier(inter, ray_origin, ray_dir))
807 }
808
809 pub fn intersects_ray(
811 &self,
812 translation: Vect,
813 rotation: Rot,
814 ray_origin: Vect,
815 ray_dir: Vect,
816 max_time_of_impact: Real,
817 ) -> bool {
818 let pos = crate::utils::pose_from(translation, rotation);
819 let ray = Ray::new(ray_origin, ray_dir);
820 self.raw.intersects_ray(&pos, &ray, max_time_of_impact)
821 }
822}
823
824impl Default for Collider {
825 fn default() -> Self {
826 Self::ball(0.5)
827 }
828}
829
830#[cfg(all(feature = "dim3", feature = "async-collider"))]
831#[allow(clippy::type_complexity)]
832fn extract_mesh_vertices_indices(mesh: &Mesh) -> Option<(Vec<Vector>, Vec<[u32; 3]>)> {
833 let vertices = mesh.attribute(Mesh::ATTRIBUTE_POSITION)?;
834 let indices = mesh.indices()?;
835
836 let vtx: Vec<Vector> = match vertices {
837 VertexAttributeValues::Float32(vtx) => Some(
838 vtx.chunks(3)
839 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
840 .collect(),
841 ),
842 VertexAttributeValues::Float32x3(vtx) => Some(
843 vtx.iter()
844 .map(|v| Vector::new(v[0] as Real, v[1] as Real, v[2] as Real))
845 .collect(),
846 ),
847 _ => None,
848 }?;
849
850 let idx = match indices {
851 Indices::U16(idx) => idx
852 .chunks_exact(3)
853 .map(|i| [i[0] as u32, i[1] as u32, i[2] as u32])
854 .collect(),
855 Indices::U32(idx) => idx.chunks_exact(3).map(|i| [i[0], i[1], i[2]]).collect(),
856 };
857
858 Some((vtx, idx))
859}