Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/crates/bevy_mesh/src/skinning.rs
9396 views
1
use crate::{Mesh, MeshVertexAttribute, VertexAttributeValues, VertexFormat};
2
use bevy_asset::{AsAssetId, Asset, AssetId, Handle};
3
use bevy_ecs::{component::Component, entity::Entity, prelude::ReflectComponent, system::Query};
4
use bevy_math::{
5
bounding::{Aabb3d, BoundingVolume},
6
Affine3A, Mat4, Vec3, Vec3A,
7
};
8
use bevy_reflect::prelude::*;
9
use bevy_transform::components::GlobalTransform;
10
use core::ops::Deref;
11
use thiserror::Error;
12
13
#[derive(Component, Debug, Default, Clone, Reflect)]
14
#[reflect(Component, Default, Debug, Clone)]
15
pub struct SkinnedMesh {
16
pub inverse_bindposes: Handle<SkinnedMeshInverseBindposes>,
17
#[entities]
18
pub joints: Vec<Entity>,
19
}
20
21
impl AsAssetId for SkinnedMesh {
22
type Asset = SkinnedMeshInverseBindposes;
23
24
// We implement this so that `AssetChanged` will work to pick up any changes
25
// to `SkinnedMeshInverseBindposes`.
26
fn as_asset_id(&self) -> AssetId<Self::Asset> {
27
self.inverse_bindposes.id()
28
}
29
}
30
31
#[derive(Asset, TypePath, Debug)]
32
pub struct SkinnedMeshInverseBindposes(Box<[Mat4]>);
33
34
impl From<Vec<Mat4>> for SkinnedMeshInverseBindposes {
35
fn from(value: Vec<Mat4>) -> Self {
36
Self(value.into_boxed_slice())
37
}
38
}
39
40
impl Deref for SkinnedMeshInverseBindposes {
41
type Target = [Mat4];
42
fn deref(&self) -> &Self::Target {
43
&self.0
44
}
45
}
46
47
// The AABB of a joint. This is optimized for `transform_aabb` - center/size is
48
// slightly faster than the min/max used by `bevy_math::Aabb3d`, and the vectors
49
// don't benefit from alignment because they're broadcast loaded.
50
#[derive(Copy, Clone, Debug, PartialEq, Reflect)]
51
pub struct JointAabb {
52
pub center: Vec3,
53
pub half_size: Vec3,
54
}
55
56
impl JointAabb {
57
fn min(&self) -> Vec3 {
58
self.center - self.half_size
59
}
60
61
fn max(&self) -> Vec3 {
62
self.center + self.half_size
63
}
64
}
65
66
impl From<JointAabb> for Aabb3d {
67
fn from(value: JointAabb) -> Self {
68
Self {
69
min: value.min().into(),
70
max: value.max().into(),
71
}
72
}
73
}
74
75
impl From<Aabb3d> for JointAabb {
76
fn from(value: Aabb3d) -> Self {
77
Self {
78
center: value.center().into(),
79
half_size: value.half_size().into(),
80
}
81
}
82
}
83
84
/// Data that can be used to calculate the AABB of a skinned mesh.
85
#[derive(Clone, Default, Debug, PartialEq, Reflect)]
86
#[reflect(Clone)]
87
pub struct SkinnedMeshBounds {
88
// Model-space AABBs that enclose the vertices skinned to a joint. Some
89
// joints may not be skinned to any vertices, so not every joint has an
90
// AABB.
91
//
92
// `aabb_index_to_joint_index` maps from an `aabbs` index to a joint index,
93
// which corresponds to `Mesh::ATTRIBUTE_JOINT_INDEX` and `SkinnedMesh::joints`.
94
//
95
// These arrays could be a single `Vec<(JointAabb, JointIndex)>`, but that
96
// would waste two bytes due to alignment.
97
//
98
// TODO: If https://github.com/bevyengine/bevy/issues/11570 is fixed, `Vec<_>`
99
// can be changed to `Box<[_]>`.
100
pub aabbs: Vec<JointAabb>,
101
pub aabb_index_to_joint_index: Vec<JointIndex>,
102
}
103
104
#[derive(Copy, Clone, PartialEq, Debug, Error)]
105
pub enum SkinnedMeshBoundsError {
106
#[error("The mesh does not contain any joints that are skinned to vertices")]
107
NoSkinnedJoints,
108
#[error(transparent)]
109
MeshAttributeError(#[from] MeshAttributeError),
110
}
111
112
impl SkinnedMeshBounds {
113
/// Create a `SkinnedMeshBounds` from a [`Mesh`].
114
///
115
/// The mesh is expected to have position, joint index and joint weight
116
/// attributes. If any are missing then a [`MeshAttributeError`] is returned.
117
pub fn from_mesh(mesh: &Mesh) -> Result<SkinnedMeshBounds, SkinnedMeshBoundsError> {
118
let vertex_positions = expect_attribute_float32x3(mesh, Mesh::ATTRIBUTE_POSITION)?;
119
let vertex_influences = InfluenceIterator::new(mesh)?;
120
121
// Find the maximum joint index.
122
let Some(max_joint_index) = vertex_influences
123
.clone()
124
.map(|i| i.joint_index.0 as usize)
125
.reduce(Ord::max)
126
else {
127
return Ok(SkinnedMeshBounds::default());
128
};
129
130
// Create an AABB accumulator for each joint.
131
let mut accumulators: Box<[AabbAccumulator]> =
132
vec![AabbAccumulator::new(); max_joint_index + 1].into();
133
134
// Iterate over all vertex influences and add the vertex position to
135
// the influencing joint's AABB.
136
for influence in vertex_influences {
137
if let Some(&vertex_position) = vertex_positions.get(influence.vertex_index) {
138
accumulators[influence.joint_index.0 as usize]
139
.add_point(Vec3A::from_array(vertex_position));
140
}
141
}
142
143
// Filter out joints with no AABB.
144
let joint_indices_and_aabbs = accumulators
145
.iter()
146
.enumerate()
147
.filter_map(|(joint_index, &accumulator)| {
148
accumulator.finish().map(|aabb| (joint_index, aabb))
149
})
150
.collect::<Vec<_>>();
151
152
if joint_indices_and_aabbs.is_empty() {
153
return Err(SkinnedMeshBoundsError::NoSkinnedJoints);
154
}
155
156
let aabbs = joint_indices_and_aabbs
157
.iter()
158
.map(|&(_, aabb)| JointAabb::from(aabb))
159
.collect::<Vec<_>>();
160
161
let aabb_index_to_joint_index = joint_indices_and_aabbs
162
.iter()
163
.map(|&(joint_index, _)| JointIndex(joint_index as u16))
164
.collect::<Vec<_>>();
165
166
assert_eq!(aabbs.len(), aabb_index_to_joint_index.len());
167
168
Ok(SkinnedMeshBounds {
169
aabbs,
170
aabb_index_to_joint_index,
171
})
172
}
173
174
pub fn iter(&self) -> impl Iterator<Item = (&JointIndex, &JointAabb)> {
175
self.aabb_index_to_joint_index.iter().zip(self.aabbs.iter())
176
}
177
}
178
179
#[derive(Copy, Clone, Debug)]
180
pub enum EntityAabbFromSkinnedMeshBoundsError {
181
OutOfRangeJointIndex(JointIndex),
182
MissingJointEntity,
183
MissingSkinnedMeshBounds,
184
}
185
186
/// Given the components of a skinned mesh entity, return an `Aabb3d` that
187
/// encloses the skinned vertices of the mesh.
188
pub fn entity_aabb_from_skinned_mesh_bounds(
189
joint_entities: &Query<&GlobalTransform>,
190
mesh: &Mesh,
191
skinned_mesh: &SkinnedMesh,
192
skinned_mesh_inverse_bindposes: &SkinnedMeshInverseBindposes,
193
world_from_entity: Option<&GlobalTransform>,
194
) -> Result<Aabb3d, EntityAabbFromSkinnedMeshBoundsError> {
195
let Some(skinned_mesh_bounds) = mesh.skinned_mesh_bounds() else {
196
return Err(EntityAabbFromSkinnedMeshBoundsError::MissingSkinnedMeshBounds);
197
};
198
199
let mut accumulator = AabbAccumulator::new();
200
201
// For each model-space joint AABB, transform it to world-space and add it
202
// to the accumulator.
203
for (&joint_index, &modelspace_joint_aabb) in skinned_mesh_bounds.iter() {
204
let Some(joint_from_model) = skinned_mesh_inverse_bindposes
205
.get(joint_index.0 as usize)
206
.map(|&m| Affine3A::from_mat4(m))
207
else {
208
return Err(EntityAabbFromSkinnedMeshBoundsError::OutOfRangeJointIndex(
209
joint_index,
210
));
211
};
212
213
let Some(&joint_entity) = skinned_mesh.joints.get(joint_index.0 as usize) else {
214
return Err(EntityAabbFromSkinnedMeshBoundsError::OutOfRangeJointIndex(
215
joint_index,
216
));
217
};
218
219
let Ok(&world_from_joint) = joint_entities.get(joint_entity) else {
220
return Err(EntityAabbFromSkinnedMeshBoundsError::MissingJointEntity);
221
};
222
223
let world_from_model = world_from_joint.affine() * joint_from_model;
224
let worldspace_joint_aabb = transform_aabb(modelspace_joint_aabb, world_from_model);
225
226
accumulator.add_aabb(worldspace_joint_aabb);
227
}
228
229
let Some(worldspace_entity_aabb) = accumulator.finish() else {
230
return Err(EntityAabbFromSkinnedMeshBoundsError::MissingJointEntity);
231
};
232
233
// If the entity has a transform, move the AABB from world-space to entity-space.
234
if let Some(world_from_entity) = world_from_entity {
235
let entityspace_entity_aabb = transform_aabb(
236
worldspace_entity_aabb.into(),
237
world_from_entity.affine().inverse(),
238
);
239
240
Ok(entityspace_entity_aabb)
241
} else {
242
Ok(worldspace_entity_aabb)
243
}
244
}
245
246
// Return the smallest `Aabb3d` that encloses the transformed `JointAabb`.
247
//
248
// Algorithm from "Transforming Axis-Aligned Bounding Boxes", James Arvo, Graphics Gems (1990).
249
#[inline]
250
fn transform_aabb(input: JointAabb, transform: Affine3A) -> Aabb3d {
251
let mx = transform.matrix3.x_axis;
252
let my = transform.matrix3.y_axis;
253
let mz = transform.matrix3.z_axis;
254
let mt = transform.translation;
255
256
let cx = Vec3A::splat(input.center.x);
257
let cy = Vec3A::splat(input.center.y);
258
let cz = Vec3A::splat(input.center.z);
259
260
let sx = Vec3A::splat(input.half_size.x);
261
let sy = Vec3A::splat(input.half_size.y);
262
let sz = Vec3A::splat(input.half_size.z);
263
264
// Transform the center.
265
let tc = (mx * cx) + (my * cy) + (mz * cz) + mt;
266
267
// Calculate a size that encloses the transformed size.
268
let ts = (mx.abs() * sx) + (my.abs() * sy) + (mz.abs() * sz);
269
270
let min = tc - ts;
271
let max = tc + ts;
272
273
Aabb3d { min, max }
274
}
275
276
// Helper for efficiently accumulating an enclosing AABB from a set of points or
277
// other AABBs. Intended for cases where the size of the set is not known in
278
// advance and might be zero.
279
//
280
// ```
281
// let a = AabbAccumulator::new();
282
//
283
// a.add_point(point); // Add a `Vec3A`.
284
// a.add_aabb(aabb); // Add an `Aabb3d`.
285
//
286
// // Returns `Some(Aabb3d)` if at least one thing was added.
287
// let result = a.finish();
288
// ```
289
//
290
// For alternatives, see [`Aabb3d::from_point_clound`](`bevy_math::bounding::bounded3d::Aabb3d::from_point_cloud`)
291
// and [`BoundingVolume::merge`](`bevy_math::bounding::BoundingVolume::merge`).
292
#[derive(Copy, Clone)]
293
struct AabbAccumulator {
294
min: Vec3A,
295
max: Vec3A,
296
}
297
298
impl AabbAccumulator {
299
fn new() -> Self {
300
// Initialize in such a way that adds can be branchless but `finish` can
301
// still detect if nothing was added. The initial state has `min > max`,
302
// but the first add will make `min <= max`.
303
Self {
304
min: Vec3A::MAX,
305
max: Vec3A::MIN,
306
}
307
}
308
309
fn add_aabb(&mut self, aabb: Aabb3d) {
310
self.min = self.min.min(aabb.min);
311
self.max = self.max.max(aabb.max);
312
}
313
314
fn add_point(&mut self, position: Vec3A) {
315
self.min = self.min.min(position);
316
self.max = self.max.max(position);
317
}
318
319
/// Returns the enclosing AABB if at least one thing was added, otherwise `None`.
320
fn finish(self) -> Option<Aabb3d> {
321
if self.min.cmpgt(self.max).any() {
322
None
323
} else {
324
Some(Aabb3d {
325
min: self.min,
326
max: self.max,
327
})
328
}
329
}
330
}
331
332
// An index that corresponds to `Mesh::ATTRIBUTE_JOINT_INDEX` and `SkinnedMesh::joints`.
333
#[derive(Copy, Clone, PartialEq, Debug, Reflect)]
334
pub struct JointIndex(pub u16);
335
336
/// A single vertex influence. Used by [`InfluenceIterator`].
337
#[derive(Copy, Clone, PartialEq, Debug)]
338
pub struct Influence {
339
pub vertex_index: usize,
340
pub joint_index: JointIndex,
341
pub joint_weight: f32,
342
}
343
344
/// Iterator over all vertex influences with non-zero weight.
345
#[derive(Clone, Debug)]
346
pub struct InfluenceIterator<'a> {
347
vertex_count: usize,
348
joint_indices: &'a [[u16; 4]],
349
joint_weights: &'a [[f32; 4]],
350
vertex_index: usize,
351
influence_index: usize,
352
}
353
354
impl<'a> InfluenceIterator<'a> {
355
pub fn new(mesh: &'a Mesh) -> Result<Self, MeshAttributeError> {
356
let joint_indices = expect_attribute_uint16x4(mesh, Mesh::ATTRIBUTE_JOINT_INDEX)?;
357
let joint_weights = expect_attribute_float32x4(mesh, Mesh::ATTRIBUTE_JOINT_WEIGHT)?;
358
359
Ok(InfluenceIterator {
360
vertex_count: joint_indices.len().min(joint_weights.len()),
361
joint_indices,
362
joint_weights,
363
vertex_index: 0,
364
influence_index: 0,
365
})
366
}
367
368
// `Mesh` only supports four influences, so we can make this const for
369
// simplicity. If `Mesh` gains support for variable influences then this
370
// will become a variable.
371
const MAX_INFLUENCES: usize = 4;
372
}
373
374
impl Iterator for InfluenceIterator<'_> {
375
type Item = Influence;
376
377
fn next(&mut self) -> Option<Influence> {
378
loop {
379
assert!(self.influence_index <= Self::MAX_INFLUENCES);
380
assert!(self.vertex_index <= self.vertex_count);
381
382
if self.influence_index >= Self::MAX_INFLUENCES {
383
self.influence_index = 0;
384
self.vertex_index += 1;
385
}
386
387
if self.vertex_index >= self.vertex_count {
388
return None;
389
}
390
391
let joint_index = self.joint_indices[self.vertex_index][self.influence_index];
392
let joint_weight = self.joint_weights[self.vertex_index][self.influence_index];
393
394
self.influence_index += 1;
395
396
if joint_weight > 0.0 {
397
return Some(Influence {
398
vertex_index: self.vertex_index,
399
joint_index: JointIndex(joint_index),
400
joint_weight,
401
});
402
}
403
}
404
}
405
}
406
407
/// Generic error for when a mesh was expected to have a certain attribute with
408
/// a certain format.
409
#[derive(Copy, Clone, PartialEq, Debug, Error)]
410
pub enum MeshAttributeError {
411
#[error("Missing attribute \"{0}\"")]
412
MissingAttribute(&'static str),
413
#[error("Attribute \"{0}\" has unexpected format {1:?}")]
414
UnexpectedFormat(&'static str, VertexFormat),
415
}
416
417
// Implements a function that returns a mesh attribute's data or `MeshAttributeError`.
418
//
419
// ```
420
// impl_expect_attribute!(expect_attribute_float32x3, Float32x3, [f32; 3]);
421
//
422
// let positions: Vec<[f32; 3]> = expect_attribute_float32x3(mesh, Mesh::ATTRIBUTE_POSITION)?;
423
// ```
424
macro_rules! impl_expect_attribute {
425
($name:ident, $value_type:ident, $output_type:ty) => {
426
fn $name<'a>(
427
mesh: &'a Mesh,
428
attribute: MeshVertexAttribute,
429
) -> Result<&'a Vec<$output_type>, MeshAttributeError> {
430
match mesh.attribute(attribute) {
431
Some(VertexAttributeValues::$value_type(v)) => Ok(v),
432
Some(v) => {
433
return Err(MeshAttributeError::UnexpectedFormat(
434
attribute.name,
435
v.into(),
436
))
437
}
438
None => return Err(MeshAttributeError::MissingAttribute(attribute.name)),
439
}
440
}
441
};
442
}
443
444
impl_expect_attribute!(expect_attribute_float32x3, Float32x3, [f32; 3]);
445
impl_expect_attribute!(expect_attribute_float32x4, Float32x4, [f32; 4]);
446
impl_expect_attribute!(expect_attribute_uint16x4, Uint16x4, [u16; 4]);
447
448
#[cfg(test)]
449
mod tests {
450
use super::*;
451
use approx::assert_abs_diff_eq;
452
use bevy_asset::RenderAssetUsages;
453
use bevy_math::{bounding::BoundingVolume, vec3, vec3a};
454
455
#[test]
456
fn aabb_accumulator() {
457
assert_eq!(AabbAccumulator::new().finish(), None);
458
459
let nice_aabbs = &[
460
Aabb3d {
461
min: vec3a(1.0, 2.0, 3.0),
462
max: vec3a(5.0, 4.0, 3.0),
463
},
464
Aabb3d {
465
min: vec3a(-99.0, 2.0, 3.0),
466
max: vec3a(5.0, 4.0, 3.0),
467
},
468
Aabb3d {
469
min: vec3a(1.0, 2.0, 3.0),
470
max: vec3a(5.0, 99.0, 3.0),
471
},
472
];
473
474
let naughty_aabbs = &[
475
Aabb3d {
476
min: Vec3A::MIN,
477
max: Vec3A::MAX,
478
},
479
Aabb3d {
480
min: Vec3A::MIN,
481
max: Vec3A::MIN,
482
},
483
Aabb3d {
484
min: Vec3A::MAX,
485
max: Vec3A::MAX,
486
},
487
];
488
489
for aabbs in [nice_aabbs, naughty_aabbs] {
490
for &aabb in aabbs {
491
let point = aabb.min;
492
493
let mut one_aabb = AabbAccumulator::new();
494
let mut one_point = AabbAccumulator::new();
495
496
one_aabb.add_aabb(aabb);
497
one_point.add_point(point);
498
499
assert_eq!(one_aabb.finish(), Some(aabb));
500
assert_eq!(
501
one_point.finish(),
502
Some(Aabb3d {
503
min: point,
504
max: point
505
})
506
);
507
}
508
509
{
510
let mut multiple_aabbs = AabbAccumulator::new();
511
let mut multiple_points = AabbAccumulator::new();
512
513
for &aabb in aabbs {
514
multiple_aabbs.add_aabb(aabb);
515
multiple_points.add_point(aabb.min);
516
multiple_points.add_point(aabb.max);
517
}
518
519
let expected = aabbs.iter().cloned().reduce(|l, r| l.merge(&r));
520
521
assert_eq!(multiple_aabbs.finish(), expected);
522
assert_eq!(multiple_points.finish(), expected);
523
}
524
}
525
}
526
527
#[test]
528
fn influence_iterator() {
529
let mesh = Mesh::new(
530
wgpu_types::PrimitiveTopology::TriangleList,
531
RenderAssetUsages::default(),
532
);
533
534
assert_eq!(
535
InfluenceIterator::new(&mesh).err(),
536
Some(MeshAttributeError::MissingAttribute(
537
Mesh::ATTRIBUTE_JOINT_INDEX.name
538
))
539
);
540
541
let mesh = mesh.with_inserted_attribute(
542
Mesh::ATTRIBUTE_JOINT_INDEX,
543
VertexAttributeValues::Uint16x4(vec![
544
[1, 0, 0, 0],
545
[0, 2, 0, 0],
546
[0, 0, 3, 0],
547
[0, 0, 0, 4],
548
[1, 2, 0, 0],
549
[3, 4, 5, 0],
550
[6, 7, 8, 9],
551
]),
552
);
553
554
assert_eq!(
555
InfluenceIterator::new(&mesh).err(),
556
Some(MeshAttributeError::MissingAttribute(
557
Mesh::ATTRIBUTE_JOINT_WEIGHT.name
558
))
559
);
560
561
let mesh = mesh.with_inserted_attribute(
562
Mesh::ATTRIBUTE_JOINT_WEIGHT,
563
VertexAttributeValues::Float32x4(vec![
564
[1.0, 0.0, 0.0, 0.0],
565
[0.0, 1.0, 0.0, 0.0],
566
[0.0, 0.0, 1.0, 0.0],
567
[0.0, 0.0, 0.0, 1.0],
568
[0.1, 0.9, 0.0, 0.0],
569
[0.1, 0.2, 0.7, 0.0],
570
[0.1, 0.2, 0.4, 0.3],
571
]),
572
);
573
574
let expected = &[
575
Influence {
576
vertex_index: 0,
577
joint_index: JointIndex(1),
578
joint_weight: 1.0,
579
},
580
Influence {
581
vertex_index: 1,
582
joint_index: JointIndex(2),
583
joint_weight: 1.0,
584
},
585
Influence {
586
vertex_index: 2,
587
joint_index: JointIndex(3),
588
joint_weight: 1.0,
589
},
590
Influence {
591
vertex_index: 3,
592
joint_index: JointIndex(4),
593
joint_weight: 1.0,
594
},
595
Influence {
596
vertex_index: 4,
597
joint_index: JointIndex(1),
598
joint_weight: 0.1,
599
},
600
Influence {
601
vertex_index: 4,
602
joint_index: JointIndex(2),
603
joint_weight: 0.9,
604
},
605
Influence {
606
vertex_index: 5,
607
joint_index: JointIndex(3),
608
joint_weight: 0.1,
609
},
610
Influence {
611
vertex_index: 5,
612
joint_index: JointIndex(4),
613
joint_weight: 0.2,
614
},
615
Influence {
616
vertex_index: 5,
617
joint_index: JointIndex(5),
618
joint_weight: 0.7,
619
},
620
Influence {
621
vertex_index: 6,
622
joint_index: JointIndex(6),
623
joint_weight: 0.1,
624
},
625
Influence {
626
vertex_index: 6,
627
joint_index: JointIndex(7),
628
joint_weight: 0.2,
629
},
630
Influence {
631
vertex_index: 6,
632
joint_index: JointIndex(8),
633
joint_weight: 0.4,
634
},
635
Influence {
636
vertex_index: 6,
637
joint_index: JointIndex(9),
638
joint_weight: 0.3,
639
},
640
];
641
642
assert_eq!(
643
InfluenceIterator::new(&mesh).unwrap().collect::<Vec<_>>(),
644
expected
645
);
646
}
647
648
fn aabb_assert_eq(a: Aabb3d, b: Aabb3d) {
649
assert_abs_diff_eq!(a.min.x, b.min.x);
650
assert_abs_diff_eq!(a.min.y, b.min.y);
651
assert_abs_diff_eq!(a.min.z, b.min.z);
652
assert_abs_diff_eq!(a.max.x, b.max.x);
653
assert_abs_diff_eq!(a.max.y, b.max.y);
654
assert_abs_diff_eq!(a.max.z, b.max.z);
655
}
656
657
// Like `transform_aabb`, but uses the naive method of transforming each corner.
658
fn naive_transform_aabb(input: JointAabb, transform: Affine3A) -> Aabb3d {
659
let minmax = [input.min(), input.max()];
660
661
let mut accumulator = AabbAccumulator::new();
662
663
for i in 0..8 {
664
let corner = vec3(
665
minmax[i & 1].x,
666
minmax[(i >> 1) & 1].y,
667
minmax[(i >> 2) & 1].z,
668
);
669
670
accumulator.add_point(transform.transform_point3(corner).into());
671
}
672
673
accumulator.finish().unwrap()
674
}
675
676
#[test]
677
fn transform_aabb() {
678
let aabbs = [
679
JointAabb {
680
center: Vec3::ZERO,
681
half_size: Vec3::ZERO,
682
},
683
JointAabb {
684
center: Vec3::ZERO,
685
half_size: vec3(2.0, 3.0, 4.0),
686
},
687
JointAabb {
688
center: vec3(2.0, 3.0, 4.0),
689
half_size: Vec3::ZERO,
690
},
691
JointAabb {
692
center: vec3(20.0, -30.0, 40.0),
693
half_size: vec3(5.0, 6.0, 7.0),
694
},
695
];
696
697
// Various transforms, including awkward ones like skews and
698
// negative/zero scales.
699
let transforms = [
700
Affine3A::IDENTITY,
701
Affine3A::from_cols(Vec3A::X, Vec3A::Z, Vec3A::Y, vec3a(1.0, 2.0, 3.0)),
702
Affine3A::from_cols(Vec3A::Y, Vec3A::X, Vec3A::Z, vec3a(1.0, 2.0, 3.0)),
703
Affine3A::from_cols(Vec3A::Z, Vec3A::Y, Vec3A::X, vec3a(1.0, 2.0, 3.0)),
704
Affine3A::from_scale(Vec3::ZERO),
705
Affine3A::from_scale(vec3(2.0, 3.0, 4.0)),
706
Affine3A::from_scale(vec3(-2.0, 3.0, -4.0)),
707
Affine3A::from_cols(
708
vec3a(1.0, 2.0, -3.0),
709
vec3a(4.0, -5.0, 6.0),
710
vec3a(-7.0, 8.0, 9.0),
711
vec3a(1.0, -2.0, 3.0),
712
),
713
];
714
715
for aabb in aabbs {
716
for transform in transforms {
717
aabb_assert_eq(
718
super::transform_aabb(aabb, transform),
719
naive_transform_aabb(aabb, transform),
720
);
721
}
722
}
723
}
724
}
725
726