1use crate::{Mesh, MeshVertexAttribute, VertexAttributeValues, VertexFormat};
2use bevy_asset::{AsAssetId, Asset, AssetId, Handle};
3use bevy_ecs::{
4 component::Component, entity::Entity, prelude::ReflectComponent, system::Query,
5 template::FromTemplate,
6};
7use bevy_math::{
8 bounding::{Aabb3d, BoundingVolume},
9 Affine3A, Mat4, Vec3, Vec3A,
10};
11use bevy_reflect::prelude::*;
12use bevy_transform::components::GlobalTransform;
13use core::ops::Deref;
14use thiserror::Error;
15
16#[derive(Component, Debug, Default, Clone, Reflect, FromTemplate)]
17#[reflect(Component, Default, Debug, Clone)]
18pub struct SkinnedMesh {
19 pub inverse_bindposes: Handle<SkinnedMeshInverseBindposes>,
20 #[entities]
21 pub joints: Vec<Entity>,
22}
23
24impl AsAssetId for SkinnedMesh {
25 type Asset = SkinnedMeshInverseBindposes;
26
27 fn as_asset_id(&self) -> AssetId<Self::Asset> {
30 self.inverse_bindposes.id()
31 }
32}
33
34#[derive(Asset, TypePath, Debug)]
35pub struct SkinnedMeshInverseBindposes(Box<[Mat4]>);
36
37impl From<Vec<Mat4>> for SkinnedMeshInverseBindposes {
38 fn from(value: Vec<Mat4>) -> Self {
39 Self(value.into_boxed_slice())
40 }
41}
42
43impl Deref for SkinnedMeshInverseBindposes {
44 type Target = [Mat4];
45 fn deref(&self) -> &Self::Target {
46 &self.0
47 }
48}
49
50#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
54pub struct JointAabb {
55 pub center: Vec3,
56 pub half_size: Vec3,
57}
58
59impl JointAabb {
60 fn min(&self) -> Vec3 {
61 self.center - self.half_size
62 }
63
64 fn max(&self) -> Vec3 {
65 self.center + self.half_size
66 }
67}
68
69impl From<JointAabb> for Aabb3d {
70 fn from(value: JointAabb) -> Self {
71 Self {
72 min: value.min().into(),
73 max: value.max().into(),
74 }
75 }
76}
77
78impl From<Aabb3d> for JointAabb {
79 fn from(value: Aabb3d) -> Self {
80 Self {
81 center: value.center().into(),
82 half_size: value.half_size().into(),
83 }
84 }
85}
86
87#[derive(Clone, Default, Debug, PartialEq, Reflect)]
89#[reflect(Clone)]
90pub struct SkinnedMeshBounds {
91 pub aabbs: Vec<JointAabb>,
104 pub aabb_index_to_joint_index: Vec<JointIndex>,
105}
106
107#[derive(Copy, Clone, PartialEq, Debug, Error)]
108pub enum SkinnedMeshBoundsError {
109 #[error("The mesh does not contain any joints that are skinned to vertices")]
110 NoSkinnedJoints,
111 #[error(transparent)]
112 MeshAttributeError(#[from] MeshAttributeError),
113}
114
115impl SkinnedMeshBounds {
116 pub fn from_mesh(mesh: &Mesh) -> Result<SkinnedMeshBounds, SkinnedMeshBoundsError> {
121 let vertex_positions = expect_attribute_float32x3(mesh, Mesh::ATTRIBUTE_POSITION)?;
122 let vertex_influences = InfluenceIterator::new(mesh)?;
123
124 let Some(max_joint_index) = vertex_influences
126 .clone()
127 .map(|i| i.joint_index.0 as usize)
128 .reduce(Ord::max)
129 else {
130 return Ok(SkinnedMeshBounds::default());
131 };
132
133 let mut accumulators: Box<[AabbAccumulator]> =
135 vec![AabbAccumulator::new(); max_joint_index + 1].into();
136
137 for influence in vertex_influences {
140 if let Some(&vertex_position) = vertex_positions.get(influence.vertex_index) {
141 accumulators[influence.joint_index.0 as usize]
142 .add_point(Vec3A::from_array(vertex_position));
143 }
144 }
145
146 let joint_indices_and_aabbs = accumulators
148 .iter()
149 .enumerate()
150 .filter_map(|(joint_index, &accumulator)| {
151 accumulator.finish().map(|aabb| (joint_index, aabb))
152 })
153 .collect::<Vec<_>>();
154
155 if joint_indices_and_aabbs.is_empty() {
156 return Err(SkinnedMeshBoundsError::NoSkinnedJoints);
157 }
158
159 let aabbs = joint_indices_and_aabbs
160 .iter()
161 .map(|&(_, aabb)| JointAabb::from(aabb))
162 .collect::<Vec<_>>();
163
164 let aabb_index_to_joint_index = joint_indices_and_aabbs
165 .iter()
166 .map(|&(joint_index, _)| JointIndex(joint_index as u16))
167 .collect::<Vec<_>>();
168
169 assert_eq!(aabbs.len(), aabb_index_to_joint_index.len());
170
171 Ok(SkinnedMeshBounds {
172 aabbs,
173 aabb_index_to_joint_index,
174 })
175 }
176
177 pub fn iter(&self) -> impl Iterator<Item = (&JointIndex, &JointAabb)> {
178 self.aabb_index_to_joint_index.iter().zip(self.aabbs.iter())
179 }
180}
181
182#[derive(Copy, Clone, Debug)]
183pub enum EntityAabbFromSkinnedMeshBoundsError {
184 OutOfRangeJointIndex(JointIndex),
185 MissingJointEntity,
186 MissingSkinnedMeshBounds,
187}
188
189pub fn entity_aabb_from_skinned_mesh_bounds(
192 joint_entities: &Query<&GlobalTransform>,
193 mesh: &Mesh,
194 skinned_mesh: &SkinnedMesh,
195 skinned_mesh_inverse_bindposes: &SkinnedMeshInverseBindposes,
196 world_from_entity: Option<&GlobalTransform>,
197) -> Result<Aabb3d, EntityAabbFromSkinnedMeshBoundsError> {
198 let Some(skinned_mesh_bounds) = mesh.skinned_mesh_bounds() else {
199 return Err(EntityAabbFromSkinnedMeshBoundsError::MissingSkinnedMeshBounds);
200 };
201
202 let mut accumulator = AabbAccumulator::new();
203
204 for (&joint_index, &modelspace_joint_aabb) in skinned_mesh_bounds.iter() {
207 let Some(joint_from_model) = skinned_mesh_inverse_bindposes
208 .get(joint_index.0 as usize)
209 .map(|&m| Affine3A::from_mat4(m))
210 else {
211 return Err(EntityAabbFromSkinnedMeshBoundsError::OutOfRangeJointIndex(
212 joint_index,
213 ));
214 };
215
216 let Some(&joint_entity) = skinned_mesh.joints.get(joint_index.0 as usize) else {
217 return Err(EntityAabbFromSkinnedMeshBoundsError::OutOfRangeJointIndex(
218 joint_index,
219 ));
220 };
221
222 let Ok(&world_from_joint) = joint_entities.get(joint_entity) else {
223 return Err(EntityAabbFromSkinnedMeshBoundsError::MissingJointEntity);
224 };
225
226 let world_from_model = world_from_joint.affine() * joint_from_model;
227 let worldspace_joint_aabb = transform_aabb(modelspace_joint_aabb, world_from_model);
228
229 accumulator.add_aabb(worldspace_joint_aabb);
230 }
231
232 let Some(worldspace_entity_aabb) = accumulator.finish() else {
233 return Err(EntityAabbFromSkinnedMeshBoundsError::MissingJointEntity);
234 };
235
236 if let Some(world_from_entity) = world_from_entity {
238 let entityspace_entity_aabb = transform_aabb(
239 worldspace_entity_aabb.into(),
240 world_from_entity.affine().inverse(),
241 );
242
243 Ok(entityspace_entity_aabb)
244 } else {
245 Ok(worldspace_entity_aabb)
246 }
247}
248
249#[inline]
253fn transform_aabb(input: JointAabb, transform: Affine3A) -> Aabb3d {
254 let mx = transform.matrix3.x_axis;
255 let my = transform.matrix3.y_axis;
256 let mz = transform.matrix3.z_axis;
257 let mt = transform.translation;
258
259 let cx = Vec3A::splat(input.center.x);
260 let cy = Vec3A::splat(input.center.y);
261 let cz = Vec3A::splat(input.center.z);
262
263 let sx = Vec3A::splat(input.half_size.x);
264 let sy = Vec3A::splat(input.half_size.y);
265 let sz = Vec3A::splat(input.half_size.z);
266
267 let tc = (mx * cx) + (my * cy) + (mz * cz) + mt;
269
270 let ts = (mx.abs() * sx) + (my.abs() * sy) + (mz.abs() * sz);
272
273 let min = tc - ts;
274 let max = tc + ts;
275
276 Aabb3d { min, max }
277}
278
279#[derive(Copy, Clone)]
296struct AabbAccumulator {
297 min: Vec3A,
298 max: Vec3A,
299}
300
301impl AabbAccumulator {
302 fn new() -> Self {
303 Self {
307 min: Vec3A::MAX,
308 max: Vec3A::MIN,
309 }
310 }
311
312 fn add_aabb(&mut self, aabb: Aabb3d) {
313 self.min = self.min.min(aabb.min);
314 self.max = self.max.max(aabb.max);
315 }
316
317 fn add_point(&mut self, position: Vec3A) {
318 self.min = self.min.min(position);
319 self.max = self.max.max(position);
320 }
321
322 fn finish(self) -> Option<Aabb3d> {
324 if self.min.cmpgt(self.max).any() {
325 None
326 } else {
327 Some(Aabb3d {
328 min: self.min,
329 max: self.max,
330 })
331 }
332 }
333}
334
335#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
337pub struct JointIndex(pub u16);
338
339#[derive(Copy, Clone, PartialEq, Debug)]
341pub struct Influence {
342 pub vertex_index: usize,
343 pub joint_index: JointIndex,
344 pub joint_weight: f32,
345}
346
347#[derive(Clone, Debug)]
349pub struct InfluenceIterator<'a> {
350 vertex_count: usize,
351 joint_indices: &'a [[u16; 4]],
352 joint_weights: &'a [[f32; 4]],
353 vertex_index: usize,
354 influence_index: usize,
355}
356
357impl<'a> InfluenceIterator<'a> {
358 pub fn new(mesh: &'a Mesh) -> Result<Self, MeshAttributeError> {
359 let joint_indices = expect_attribute_uint16x4(mesh, Mesh::ATTRIBUTE_JOINT_INDEX)?;
360 let joint_weights = expect_attribute_float32x4(mesh, Mesh::ATTRIBUTE_JOINT_WEIGHT)?;
361
362 Ok(InfluenceIterator {
363 vertex_count: joint_indices.len().min(joint_weights.len()),
364 joint_indices,
365 joint_weights,
366 vertex_index: 0,
367 influence_index: 0,
368 })
369 }
370
371 const MAX_INFLUENCES: usize = 4;
375}
376
377impl Iterator for InfluenceIterator<'_> {
378 type Item = Influence;
379
380 fn next(&mut self) -> Option<Influence> {
381 loop {
382 assert!(self.influence_index <= Self::MAX_INFLUENCES);
383 assert!(self.vertex_index <= self.vertex_count);
384
385 if self.influence_index >= Self::MAX_INFLUENCES {
386 self.influence_index = 0;
387 self.vertex_index += 1;
388 }
389
390 if self.vertex_index >= self.vertex_count {
391 return None;
392 }
393
394 let joint_index = self.joint_indices[self.vertex_index][self.influence_index];
395 let joint_weight = self.joint_weights[self.vertex_index][self.influence_index];
396
397 self.influence_index += 1;
398
399 if joint_weight > 0.0 {
400 return Some(Influence {
401 vertex_index: self.vertex_index,
402 joint_index: JointIndex(joint_index),
403 joint_weight,
404 });
405 }
406 }
407 }
408}
409
410#[derive(Copy, Clone, PartialEq, Debug, Error)]
413pub enum MeshAttributeError {
414 #[error("Missing attribute \"{0}\"")]
415 MissingAttribute(&'static str),
416 #[error("Attribute \"{0}\" has unexpected format {1:?}")]
417 UnexpectedFormat(&'static str, VertexFormat),
418}
419
420macro_rules! impl_expect_attribute {
428 ($name:ident, $value_type:ident, $output_type:ty) => {
429 fn $name<'a>(
430 mesh: &'a Mesh,
431 attribute: MeshVertexAttribute,
432 ) -> Result<&'a Vec<$output_type>, MeshAttributeError> {
433 match mesh.attribute(attribute) {
434 Some(VertexAttributeValues::$value_type(v)) => Ok(v),
435 Some(v) => {
436 return Err(MeshAttributeError::UnexpectedFormat(
437 attribute.name,
438 v.into(),
439 ))
440 }
441 None => return Err(MeshAttributeError::MissingAttribute(attribute.name)),
442 }
443 }
444 };
445}
446
447impl_expect_attribute!(expect_attribute_float32x3, Float32x3, [f32; 3]);
448impl_expect_attribute!(expect_attribute_float32x4, Float32x4, [f32; 4]);
449impl_expect_attribute!(expect_attribute_uint16x4, Uint16x4, [u16; 4]);
450
451#[cfg(test)]
452mod tests {
453 use super::*;
454 use approx::assert_abs_diff_eq;
455 use bevy_asset::RenderAssetUsages;
456 use bevy_math::{bounding::BoundingVolume, vec3, vec3a};
457
458 #[test]
459 fn aabb_accumulator() {
460 assert_eq!(AabbAccumulator::new().finish(), None);
461
462 let nice_aabbs = &[
463 Aabb3d {
464 min: vec3a(1.0, 2.0, 3.0),
465 max: vec3a(5.0, 4.0, 3.0),
466 },
467 Aabb3d {
468 min: vec3a(-99.0, 2.0, 3.0),
469 max: vec3a(5.0, 4.0, 3.0),
470 },
471 Aabb3d {
472 min: vec3a(1.0, 2.0, 3.0),
473 max: vec3a(5.0, 99.0, 3.0),
474 },
475 ];
476
477 let naughty_aabbs = &[
478 Aabb3d {
479 min: Vec3A::MIN,
480 max: Vec3A::MAX,
481 },
482 Aabb3d {
483 min: Vec3A::MIN,
484 max: Vec3A::MIN,
485 },
486 Aabb3d {
487 min: Vec3A::MAX,
488 max: Vec3A::MAX,
489 },
490 ];
491
492 for aabbs in [nice_aabbs, naughty_aabbs] {
493 for &aabb in aabbs {
494 let point = aabb.min;
495
496 let mut one_aabb = AabbAccumulator::new();
497 let mut one_point = AabbAccumulator::new();
498
499 one_aabb.add_aabb(aabb);
500 one_point.add_point(point);
501
502 assert_eq!(one_aabb.finish(), Some(aabb));
503 assert_eq!(
504 one_point.finish(),
505 Some(Aabb3d {
506 min: point,
507 max: point
508 })
509 );
510 }
511
512 {
513 let mut multiple_aabbs = AabbAccumulator::new();
514 let mut multiple_points = AabbAccumulator::new();
515
516 for &aabb in aabbs {
517 multiple_aabbs.add_aabb(aabb);
518 multiple_points.add_point(aabb.min);
519 multiple_points.add_point(aabb.max);
520 }
521
522 let expected = aabbs.iter().cloned().reduce(|l, r| l.merge(&r));
523
524 assert_eq!(multiple_aabbs.finish(), expected);
525 assert_eq!(multiple_points.finish(), expected);
526 }
527 }
528 }
529
530 #[test]
531 fn influence_iterator() {
532 let mesh = Mesh::new(
533 wgpu_types::PrimitiveTopology::TriangleList,
534 RenderAssetUsages::default(),
535 );
536
537 assert_eq!(
538 InfluenceIterator::new(&mesh).err(),
539 Some(MeshAttributeError::MissingAttribute(
540 Mesh::ATTRIBUTE_JOINT_INDEX.name
541 ))
542 );
543
544 let mesh = mesh.with_inserted_attribute(
545 Mesh::ATTRIBUTE_JOINT_INDEX,
546 VertexAttributeValues::Uint16x4(vec![
547 [1, 0, 0, 0],
548 [0, 2, 0, 0],
549 [0, 0, 3, 0],
550 [0, 0, 0, 4],
551 [1, 2, 0, 0],
552 [3, 4, 5, 0],
553 [6, 7, 8, 9],
554 ]),
555 );
556
557 assert_eq!(
558 InfluenceIterator::new(&mesh).err(),
559 Some(MeshAttributeError::MissingAttribute(
560 Mesh::ATTRIBUTE_JOINT_WEIGHT.name
561 ))
562 );
563
564 let mesh = mesh.with_inserted_attribute(
565 Mesh::ATTRIBUTE_JOINT_WEIGHT,
566 VertexAttributeValues::Float32x4(vec![
567 [1.0, 0.0, 0.0, 0.0],
568 [0.0, 1.0, 0.0, 0.0],
569 [0.0, 0.0, 1.0, 0.0],
570 [0.0, 0.0, 0.0, 1.0],
571 [0.1, 0.9, 0.0, 0.0],
572 [0.1, 0.2, 0.7, 0.0],
573 [0.1, 0.2, 0.4, 0.3],
574 ]),
575 );
576
577 let expected = &[
578 Influence {
579 vertex_index: 0,
580 joint_index: JointIndex(1),
581 joint_weight: 1.0,
582 },
583 Influence {
584 vertex_index: 1,
585 joint_index: JointIndex(2),
586 joint_weight: 1.0,
587 },
588 Influence {
589 vertex_index: 2,
590 joint_index: JointIndex(3),
591 joint_weight: 1.0,
592 },
593 Influence {
594 vertex_index: 3,
595 joint_index: JointIndex(4),
596 joint_weight: 1.0,
597 },
598 Influence {
599 vertex_index: 4,
600 joint_index: JointIndex(1),
601 joint_weight: 0.1,
602 },
603 Influence {
604 vertex_index: 4,
605 joint_index: JointIndex(2),
606 joint_weight: 0.9,
607 },
608 Influence {
609 vertex_index: 5,
610 joint_index: JointIndex(3),
611 joint_weight: 0.1,
612 },
613 Influence {
614 vertex_index: 5,
615 joint_index: JointIndex(4),
616 joint_weight: 0.2,
617 },
618 Influence {
619 vertex_index: 5,
620 joint_index: JointIndex(5),
621 joint_weight: 0.7,
622 },
623 Influence {
624 vertex_index: 6,
625 joint_index: JointIndex(6),
626 joint_weight: 0.1,
627 },
628 Influence {
629 vertex_index: 6,
630 joint_index: JointIndex(7),
631 joint_weight: 0.2,
632 },
633 Influence {
634 vertex_index: 6,
635 joint_index: JointIndex(8),
636 joint_weight: 0.4,
637 },
638 Influence {
639 vertex_index: 6,
640 joint_index: JointIndex(9),
641 joint_weight: 0.3,
642 },
643 ];
644
645 assert_eq!(
646 InfluenceIterator::new(&mesh).unwrap().collect::<Vec<_>>(),
647 expected
648 );
649 }
650
651 fn aabb_assert_eq(a: Aabb3d, b: Aabb3d) {
652 assert_abs_diff_eq!(a.min.x, b.min.x);
653 assert_abs_diff_eq!(a.min.y, b.min.y);
654 assert_abs_diff_eq!(a.min.z, b.min.z);
655 assert_abs_diff_eq!(a.max.x, b.max.x);
656 assert_abs_diff_eq!(a.max.y, b.max.y);
657 assert_abs_diff_eq!(a.max.z, b.max.z);
658 }
659
660 fn naive_transform_aabb(input: JointAabb, transform: Affine3A) -> Aabb3d {
662 let minmax = [input.min(), input.max()];
663
664 let mut accumulator = AabbAccumulator::new();
665
666 for i in 0..8 {
667 let corner = vec3(
668 minmax[i & 1].x,
669 minmax[(i >> 1) & 1].y,
670 minmax[(i >> 2) & 1].z,
671 );
672
673 accumulator.add_point(transform.transform_point3(corner).into());
674 }
675
676 accumulator.finish().unwrap()
677 }
678
679 #[test]
680 fn transform_aabb() {
681 let aabbs = [
682 JointAabb {
683 center: Vec3::ZERO,
684 half_size: Vec3::ZERO,
685 },
686 JointAabb {
687 center: Vec3::ZERO,
688 half_size: vec3(2.0, 3.0, 4.0),
689 },
690 JointAabb {
691 center: vec3(2.0, 3.0, 4.0),
692 half_size: Vec3::ZERO,
693 },
694 JointAabb {
695 center: vec3(20.0, -30.0, 40.0),
696 half_size: vec3(5.0, 6.0, 7.0),
697 },
698 ];
699
700 let transforms = [
703 Affine3A::IDENTITY,
704 Affine3A::from_cols(Vec3A::X, Vec3A::Z, Vec3A::Y, vec3a(1.0, 2.0, 3.0)),
705 Affine3A::from_cols(Vec3A::Y, Vec3A::X, Vec3A::Z, vec3a(1.0, 2.0, 3.0)),
706 Affine3A::from_cols(Vec3A::Z, Vec3A::Y, Vec3A::X, vec3a(1.0, 2.0, 3.0)),
707 Affine3A::from_scale(Vec3::ZERO),
708 Affine3A::from_scale(vec3(2.0, 3.0, 4.0)),
709 Affine3A::from_scale(vec3(-2.0, 3.0, -4.0)),
710 Affine3A::from_cols(
711 vec3a(1.0, 2.0, -3.0),
712 vec3a(4.0, -5.0, 6.0),
713 vec3a(-7.0, 8.0, 9.0),
714 vec3a(1.0, -2.0, 3.0),
715 ),
716 ];
717
718 for aabb in aabbs {
719 for transform in transforms {
720 aabb_assert_eq(
721 super::transform_aabb(aabb, transform),
722 naive_transform_aabb(aabb, transform),
723 );
724 }
725 }
726 }
727}