1use nalgebra::SVector;
5use symtropy_math::{Bivector, Point, Shape, Sphere, Transform};
6
7#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
9pub struct BodyHandle(pub usize);
10
11#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)]
16pub struct NetId(pub u64);
17
18#[derive(Clone, Copy, Debug, PartialEq, Eq)]
20pub enum BodyType {
21 Dynamic,
23 Static,
25 Kinematic,
27}
28
29pub struct RigidBody<const D: usize> {
31 pub handle: BodyHandle,
32 pub net_id: Option<NetId>,
34 pub body_type: BodyType,
35 pub transform: Transform<D>,
36 pub linear_velocity: SVector<f64, D>,
37 pub angular_velocity: Bivector<D>,
38 pub mass: f64,
39 pub inv_mass: f64,
40 pub inertia: SVector<f64, D>,
44 pub inv_inertia: SVector<f64, D>,
46 pub restitution: f64,
47 pub friction: f64,
48 pub collider: Box<dyn Shape<D>>,
50 pub force_accumulator: SVector<f64, D>,
52 pub torque_accumulator: Bivector<D>,
54 pub linear_damping: f64,
56 pub angular_damping: f64,
58 pub sleeping: bool,
60 pub sleep_counter: u32,
62 pub collision_group: u32,
65 pub collision_mask: u32,
68 pub is_sensor: bool,
70}
71
72impl<const D: usize> RigidBody<D> {
73 pub fn dynamic_sphere(handle: BodyHandle, position: Point<D>, radius: f64, mass: f64) -> Self {
75 Self {
76 handle,
77 net_id: None,
78 body_type: BodyType::Dynamic,
79 transform: Transform::from_translation(position),
80 linear_velocity: SVector::zeros(),
81 angular_velocity: Bivector::zero(),
82 mass,
83 inv_mass: 1.0 / mass,
84 inertia: SVector::from_element(0.4 * mass * radius * radius), inv_inertia: SVector::from_element(1.0 / (0.4 * mass * radius * radius)),
86 restitution: 0.5,
87 friction: 0.3,
88 collider: Box::new(Sphere::new(Point::origin(), radius)),
89 force_accumulator: SVector::zeros(),
90 torque_accumulator: Bivector::zero(),
91 linear_damping: 0.01,
92 angular_damping: 0.05,
93 sleeping: false,
94 sleep_counter: 0,
95 collision_group: 0xFFFF_FFFF,
96 collision_mask: 0xFFFF_FFFF,
97 is_sensor: false,
98 }
99 }
100
101 pub fn static_body(handle: BodyHandle, position: Point<D>, collider: Box<dyn Shape<D>>) -> Self {
103 Self {
104 handle,
105 net_id: None,
106 body_type: BodyType::Static,
107 transform: Transform::from_translation(position),
108 linear_velocity: SVector::zeros(),
109 angular_velocity: Bivector::zero(),
110 mass: f64::INFINITY,
111 inv_mass: 0.0,
112 inertia: SVector::from_element(f64::INFINITY),
113 inv_inertia: SVector::zeros(),
114 restitution: 0.5,
115 friction: 0.5,
116 collider,
117 force_accumulator: SVector::zeros(),
118 torque_accumulator: Bivector::zero(),
119 linear_damping: 0.0,
120 angular_damping: 0.0,
121 sleeping: false,
122 sleep_counter: 0,
123 collision_group: 0xFFFF_FFFF,
124 collision_mask: 0xFFFF_FFFF,
125 is_sensor: false,
126 }
127 }
128
129 pub fn dynamic(
131 handle: BodyHandle,
132 position: Point<D>,
133 mass: f64,
134 inertia: f64,
135 collider: Box<dyn Shape<D>>,
136 ) -> Self {
137 Self {
138 handle,
139 net_id: None,
140 body_type: BodyType::Dynamic,
141 transform: Transform::from_translation(position),
142 linear_velocity: SVector::zeros(),
143 angular_velocity: Bivector::zero(),
144 mass,
145 inv_mass: 1.0 / mass,
146 inertia: SVector::from_element(inertia),
147 inv_inertia: SVector::from_element(1.0 / inertia),
148 restitution: 0.5,
149 friction: 0.3,
150 collider,
151 force_accumulator: SVector::zeros(),
152 torque_accumulator: Bivector::zero(),
153 linear_damping: 0.01,
154 angular_damping: 0.05,
155 sleeping: false,
156 sleep_counter: 0,
157 collision_group: 0xFFFF_FFFF,
158 collision_mask: 0xFFFF_FFFF,
159 is_sensor: false,
160 }
161 }
162
163 #[inline]
165 pub fn apply_force(&mut self, force: SVector<f64, D>) {
166 self.force_accumulator += force;
167 }
168
169 #[inline]
171 pub fn apply_torque(&mut self, torque: Bivector<D>) {
172 self.torque_accumulator = self.torque_accumulator.add(&torque);
173 }
174
175 #[inline]
177 pub fn clear_accumulators(&mut self) {
178 self.force_accumulator = SVector::zeros();
179 self.torque_accumulator = Bivector::zero();
180 }
181
182 #[inline]
184 pub fn position(&self) -> &Point<D> {
185 &self.transform.translation
186 }
187
188 #[inline]
190 pub fn is_dynamic(&self) -> bool {
191 self.body_type == BodyType::Dynamic
192 }
193
194 #[inline]
196 pub fn wake(&mut self) {
197 self.sleeping = false;
198 self.sleep_counter = 0;
199 }
200
201 pub fn try_sleep(&mut self, threshold: f64, ticks_required: u32) -> bool {
204 if !self.is_dynamic() || self.sleeping {
205 return false;
206 }
207 let speed = self.linear_velocity.norm() + self.angular_velocity.norm();
208 if speed < threshold {
209 self.sleep_counter += 1;
210 if self.sleep_counter >= ticks_required {
211 self.sleeping = true;
212 self.linear_velocity = SVector::zeros();
213 self.angular_velocity = Bivector::zero();
214 return true;
215 }
216 } else {
217 self.sleep_counter = 0;
218 }
219 false
220 }
221
222 pub fn kinetic_energy(&self) -> f64 {
225 let linear = 0.5 * self.mass * self.linear_velocity.norm_squared();
226 let i_avg = self.inertia.sum() / D as f64;
227 let angular = 0.5 * i_avg * self.angular_velocity.norm_squared();
228 linear + angular
229 }
230
231 pub fn world_support(&self, direction: &SVector<f64, D>) -> SVector<f64, D> {
233 let local_dir = self.transform.rotation.reverse().rotate_vector(direction);
235 let local_support = self.collider.support(&local_dir);
237 self.transform.transform_point(&Point(local_support)).0
239 }
240}
241
242#[cfg(test)]
243mod tests {
244 use super::*;
245
246 #[test]
247 fn dynamic_sphere_creation() {
248 let body = RigidBody::<3>::dynamic_sphere(
249 BodyHandle(0),
250 Point::new([1.0, 2.0, 3.0]),
251 1.0,
252 10.0,
253 );
254 assert!(body.is_dynamic());
255 assert!((body.mass - 10.0).abs() < 1e-12);
256 assert!((body.inv_mass - 0.1).abs() < 1e-12);
257 }
258
259 #[test]
260 fn static_body_infinite_mass() {
261 let body = RigidBody::<3>::static_body(
262 BodyHandle(0),
263 Point::origin(),
264 Box::new(Sphere::<3>::unit()),
265 );
266 assert!(!body.is_dynamic());
267 assert_eq!(body.inv_mass, 0.0);
268 }
269
270 #[test]
271 fn force_accumulation() {
272 let mut body = RigidBody::<3>::dynamic_sphere(
273 BodyHandle(0),
274 Point::origin(),
275 1.0,
276 1.0,
277 );
278 body.apply_force(SVector::from([1.0, 0.0, 0.0]));
279 body.apply_force(SVector::from([0.0, 2.0, 0.0]));
280 assert!((body.force_accumulator[0] - 1.0).abs() < 1e-12);
281 assert!((body.force_accumulator[1] - 2.0).abs() < 1e-12);
282 body.clear_accumulators();
283 assert!(body.force_accumulator.norm() < 1e-12);
284 }
285
286 #[test]
287 fn kinetic_energy_at_rest_is_zero() {
288 let body = RigidBody::<4>::dynamic_sphere(
289 BodyHandle(0),
290 Point::origin(),
291 1.0,
292 1.0,
293 );
294 assert!(body.kinetic_energy() < 1e-12);
295 }
296
297 #[test]
298 fn world_support_translated() {
299 let body = RigidBody::<3>::dynamic_sphere(
300 BodyHandle(0),
301 Point::new([10.0, 0.0, 0.0]),
302 1.0,
303 1.0,
304 );
305 let dir = SVector::from([1.0, 0.0, 0.0]);
306 let sp = body.world_support(&dir);
307 assert!((sp[0] - 11.0).abs() < 1e-10);
309 }
310}