1#[derive(Clone, Copy, Debug, PartialEq, Eq)]
2pub struct ShapeSourceHandle {
3 pub id: u32,
4 pub generation: u32,
5}
6
7#[derive(Clone, Copy, Debug, PartialEq)]
8pub enum Shape {
9 Sphere { radius: f32 },
10 Box { half_extents: [f32; 3] },
11 Capsule { radius: f32, half_height: f32 },
12 Cylinder { radius: f32, half_height: f32 },
13 Hull(ShapeSourceHandle),
14 Mesh(ShapeSourceHandle),
15 HeightField(ShapeSourceHandle),
16}
17
18impl Shape {
19 pub fn sphere(radius: f32) -> Self {
20 assert!(radius > 0.0, "shape radius must be strictly positive");
21 Self::Sphere { radius }
22 }
23
24 pub fn cuboid(half_extents: [f32; 3]) -> Self {
25 assert!(
26 half_extents.iter().all(|extent| *extent > 0.0),
27 "box half extents must be strictly positive"
28 );
29 Self::Box { half_extents }
30 }
31
32 pub fn capsule(radius: f32, half_height: f32) -> Self {
33 assert!(radius > 0.0, "capsule radius must be strictly positive");
34 assert!(
35 half_height >= 0.0,
36 "capsule half height must be non-negative"
37 );
38 Self::Capsule {
39 radius,
40 half_height,
41 }
42 }
43
44 pub fn cylinder(radius: f32, half_height: f32) -> Self {
45 assert!(radius > 0.0, "cylinder radius must be strictly positive");
46 assert!(
47 half_height >= 0.0,
48 "cylinder half height must be non-negative"
49 );
50 Self::Cylinder {
51 radius,
52 half_height,
53 }
54 }
55
56 pub fn hull(source: ShapeSourceHandle) -> Self {
57 Self::Hull(source)
58 }
59
60 pub fn mesh(source: ShapeSourceHandle) -> Self {
61 Self::Mesh(source)
62 }
63
64 pub fn height_field(source: ShapeSourceHandle) -> Self {
65 Self::HeightField(source)
66 }
67
68 pub fn bounding_radius(&self) -> f32 {
69 match *self {
70 Self::Sphere { radius } => radius,
71 Self::Box { half_extents } => {
72 let x = half_extents[0];
73 let y = half_extents[1];
74 let z = half_extents[2];
75 (x * x + y * y + z * z).sqrt()
76 }
77 Self::Capsule {
78 radius,
79 half_height,
80 }
81 | Self::Cylinder {
82 radius,
83 half_height,
84 } => (half_height * half_height + radius * radius).sqrt(),
85 Self::Hull(_) | Self::Mesh(_) | Self::HeightField(_) => 0.0,
86 }
87 }
88
89 pub fn is_world_geometry(&self) -> bool {
90 matches!(self, Self::Mesh(_) | Self::HeightField(_))
91 }
92
93 pub fn is_convex(&self) -> bool {
94 !matches!(self, Self::Mesh(_) | Self::HeightField(_))
95 }
96}
97
98pub fn inverse_inertia_diagonal(
99 shape: &Shape,
100 inverse_mass: f32,
101 world_bounds: Option<([f32; 3], [f32; 3])>,
102) -> [f32; 3] {
103 if inverse_mass == 0.0 {
104 return [0.0; 3];
105 }
106 let mass = 1.0 / inverse_mass;
107 match *shape {
108 Shape::Sphere { radius } => {
109 let i = 2.0 / 5.0 * mass * radius * radius;
110 [1.0 / i; 3]
111 }
112 Shape::Box { half_extents } => {
113 let hx = half_extents[0];
114 let hy = half_extents[1];
115 let hz = half_extents[2];
116 let ex = 2.0 * hx;
117 let ey = 2.0 * hy;
118 let ez = 2.0 * hz;
119 let ix = mass / 12.0 * (ey * ey + ez * ez);
120 let iy = mass / 12.0 * (ex * ex + ez * ez);
121 let iz = mass / 12.0 * (ex * ex + ey * ey);
122 [1.0 / ix, 1.0 / iy, 1.0 / iz]
123 }
124 Shape::Capsule {
125 radius,
126 half_height,
127 } => {
128 let r = radius;
129 let h = half_height;
130 let cylinder_volume = std::f32::consts::PI * r * r * 2.0 * h;
131 let sphere_volume = 4.0 / 3.0 * std::f32::consts::PI * r * r * r;
132 let total = cylinder_volume + sphere_volume;
133 let cylinder_mass = mass * cylinder_volume / total;
134 let sphere_mass = mass * sphere_volume / total;
135 let ix = cylinder_mass / 12.0 * (3.0 * r * r + (2.0 * h) * (2.0 * h))
136 + sphere_mass
137 * (2.0 / 5.0 * r * r + (h + 3.0 / 8.0 * r) * (h + 3.0 / 8.0 * r))
138 * 2.0;
139 let iy = cylinder_mass / 2.0 * r * r + sphere_mass * 2.0 / 5.0 * r * r * 2.0;
140 [1.0 / ix, 1.0 / iy, 1.0 / ix]
141 }
142 Shape::Cylinder {
143 radius,
144 half_height,
145 } => {
146 let h = 2.0 * half_height;
147 let ix = mass / 12.0 * (3.0 * radius * radius + h * h);
148 let iy = 0.5 * mass * radius * radius;
149 [1.0 / ix, 1.0 / iy, 1.0 / ix]
150 }
151 Shape::Hull(_) | Shape::Mesh(_) | Shape::HeightField(_) => {
152 let (min, max) = world_bounds.expect("world geometry inertia requires bounds");
153 let ex = max[0] - min[0];
154 let ey = max[1] - min[1];
155 let ez = max[2] - min[2];
156 let ix = mass / 12.0 * (ey * ey + ez * ez);
157 let iy = mass / 12.0 * (ex * ex + ez * ez);
158 let iz = mass / 12.0 * (ex * ex + ey * ey);
159 [1.0 / ix, 1.0 / iy, 1.0 / iz]
160 }
161 }
162}