Skip to main content

dynamis_model/
shape.rs

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}