1#![allow(clippy::needless_range_loop)]
6#[allow(unused_imports)]
7use super::functions::*;
8pub struct IntegratePositionKernel;
10pub struct ConstraintSolverKernel;
15impl ConstraintSolverKernel {
16 pub fn solve_distance_constraints(
21 positions: &mut [[f64; 3]],
22 inv_masses: &[f64],
23 constraints: &[DistanceConstraint],
24 dt: f64,
25 ) {
26 let alpha = 1.0 / (dt * dt);
27 for c in constraints {
28 let a = c.body_a;
29 let b = c.body_b;
30 let w_a = inv_masses[a];
31 let w_b = inv_masses[b];
32 let w_total = w_a + w_b;
33 if w_total < 1e-30 {
34 continue;
35 }
36 let dx = [
37 positions[b][0] - positions[a][0],
38 positions[b][1] - positions[a][1],
39 positions[b][2] - positions[a][2],
40 ];
41 let dist = (dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2]).sqrt();
42 if dist < 1e-30 {
43 continue;
44 }
45 let err = dist - c.rest_length;
46 let tilde_compliance = c.compliance * alpha;
47 let lambda = -err / (w_total + tilde_compliance);
48 let correction = lambda / dist;
49 for k in 0..3 {
50 positions[a][k] -= w_a * correction * dx[k];
51 positions[b][k] += w_b * correction * dx[k];
52 }
53 }
54 }
55}
56#[derive(Debug, Clone, Copy)]
58pub struct SleepParams {
59 pub linear_threshold: f64,
61 pub angular_threshold: f64,
63 pub sleep_frames: u32,
65}
66#[derive(Debug, Clone, Copy, PartialEq)]
70pub struct RigidBodyState {
71 pub position: [f64; 3],
73 pub velocity: [f64; 3],
75 pub orientation: [f64; 4],
77 pub angular_velocity: [f64; 3],
79 pub inverse_mass: f64,
81}
82impl RigidBodyState {
83 pub fn at_rest(position: [f64; 3], inverse_mass: f64) -> Self {
85 Self {
86 position,
87 velocity: [0.0; 3],
88 orientation: [0.0, 0.0, 0.0, 1.0],
89 angular_velocity: [0.0; 3],
90 inverse_mass,
91 }
92 }
93}
94#[derive(Debug, Clone, Copy, PartialEq, Eq)]
96pub enum SleepState {
97 Awake,
99 Sleeping,
101}
102pub struct ContactBatchProcessor {
108 pub restitution: f64,
110 pub baumgarte: f64,
112}
113impl ContactBatchProcessor {
114 pub fn new(restitution: f64, baumgarte: f64) -> Self {
116 Self {
117 restitution,
118 baumgarte,
119 }
120 }
121 #[allow(clippy::too_many_arguments)]
126 pub fn process_contacts(
127 &self,
128 contacts: &[ContactPoint],
129 positions: &[[f64; 3]],
130 velocities: &[[f64; 3]],
131 inv_masses: &[f64],
132 n_bodies: usize,
133 dt: f64,
134 ) -> Vec<AccumulatedImpulse> {
135 let mut impulses = vec![AccumulatedImpulse::default(); n_bodies];
136 for c in contacts {
137 let a = c.body_a;
138 let b = c.body_b;
139 let w_a = inv_masses[a];
140 let w_b = inv_masses[b];
141 let w_total = w_a + w_b;
142 if w_total < 1e-30 {
143 continue;
144 }
145 let corr = self.baumgarte * c.depth / (w_total * dt);
146 let rel_vel: f64 = (0..3)
147 .map(|k| (velocities[b][k] - velocities[a][k]) * c.normal[k])
148 .sum();
149 let j = if rel_vel < 0.0 {
150 -(1.0 + self.restitution) * rel_vel / w_total
151 } else {
152 0.0
153 };
154 let impulse_a = [
155 -w_a * (j + corr) * c.normal[0],
156 -w_a * (j + corr) * c.normal[1],
157 -w_a * (j + corr) * c.normal[2],
158 ];
159 let impulse_b = [
160 w_b * (j + corr) * c.normal[0],
161 w_b * (j + corr) * c.normal[1],
162 w_b * (j + corr) * c.normal[2],
163 ];
164 for k in 0..3 {
165 impulses[a].linear[k] += impulse_a[k];
166 impulses[b].linear[k] += impulse_b[k];
167 }
168 let ra = [
169 c.position[0] - positions[a][0],
170 c.position[1] - positions[a][1],
171 c.position[2] - positions[a][2],
172 ];
173 let rb = [
174 c.position[0] - positions[b][0],
175 c.position[1] - positions[b][1],
176 c.position[2] - positions[b][2],
177 ];
178 let ang_a = [
179 ra[1] * impulse_a[2] - ra[2] * impulse_a[1],
180 ra[2] * impulse_a[0] - ra[0] * impulse_a[2],
181 ra[0] * impulse_a[1] - ra[1] * impulse_a[0],
182 ];
183 let ang_b = [
184 rb[1] * impulse_b[2] - rb[2] * impulse_b[1],
185 rb[2] * impulse_b[0] - rb[0] * impulse_b[2],
186 rb[0] * impulse_b[1] - rb[1] * impulse_b[0],
187 ];
188 impulses[a].add_angular(ang_a);
189 impulses[b].add_angular(ang_b);
190 }
191 impulses
192 }
193}
194pub struct IslandSolver {
199 pub island_ids: Vec<usize>,
201 pub num_islands: usize,
203}
204impl IslandSolver {
205 pub fn build(num_bodies: usize, constraints: &[(usize, usize)]) -> Self {
207 let mut parent: Vec<usize> = (0..num_bodies).collect();
208 let mut rank = vec![0usize; num_bodies];
209 let find = |parent: &mut Vec<usize>, mut x: usize| -> usize {
210 while parent[x] != x {
211 parent[x] = parent[parent[x]];
212 x = parent[x];
213 }
214 x
215 };
216 for &(a, b) in constraints {
217 let ra = find(&mut parent, a);
218 let rb = find(&mut parent, b);
219 if ra != rb {
220 if rank[ra] < rank[rb] {
221 parent[ra] = rb;
222 } else if rank[ra] > rank[rb] {
223 parent[rb] = ra;
224 } else {
225 parent[rb] = ra;
226 rank[ra] += 1;
227 }
228 }
229 }
230 let mut island_map = std::collections::HashMap::new();
231 let mut island_ids = vec![0usize; num_bodies];
232 let mut next_id = 0usize;
233 for i in 0..num_bodies {
234 let root = find(&mut parent, i);
235 let id = *island_map.entry(root).or_insert_with(|| {
236 let id = next_id;
237 next_id += 1;
238 id
239 });
240 island_ids[i] = id;
241 }
242 Self {
243 island_ids,
244 num_islands: next_id,
245 }
246 }
247 pub fn bodies_in_island(&self, island_id: usize) -> Vec<usize> {
249 self.island_ids
250 .iter()
251 .enumerate()
252 .filter(|&(_, id)| *id == island_id)
253 .map(|(i, _)| i)
254 .collect()
255 }
256}
257pub struct SleepTest {
259 pub dormant_frames: Vec<u32>,
261 pub sleep_states: Vec<SleepState>,
263}
264impl SleepTest {
265 pub fn new(n: usize) -> Self {
267 Self {
268 dormant_frames: vec![0; n],
269 sleep_states: vec![SleepState::Awake; n],
270 }
271 }
272 pub fn update(&mut self, states: &[RigidBodyState], params: &SleepParams) -> usize {
276 let mut newly_sleeping = 0;
277 for (i, s) in states.iter().enumerate() {
278 if s.inverse_mass < 1e-30 {
279 self.sleep_states[i] = SleepState::Sleeping;
280 continue;
281 }
282 let lin_sq: f64 = s.velocity.iter().map(|v| v * v).sum();
283 let ang_sq: f64 = s.angular_velocity.iter().map(|w| w * w).sum();
284 let dormant = lin_sq < params.linear_threshold * params.linear_threshold
285 && ang_sq < params.angular_threshold * params.angular_threshold;
286 if dormant {
287 self.dormant_frames[i] += 1;
288 if self.dormant_frames[i] >= params.sleep_frames
289 && self.sleep_states[i] == SleepState::Awake
290 {
291 self.sleep_states[i] = SleepState::Sleeping;
292 newly_sleeping += 1;
293 }
294 } else {
295 self.dormant_frames[i] = 0;
296 self.sleep_states[i] = SleepState::Awake;
297 }
298 }
299 newly_sleeping
300 }
301 pub fn wake_all(&mut self) {
303 for s in &mut self.sleep_states {
304 *s = SleepState::Awake;
305 }
306 for f in &mut self.dormant_frames {
307 *f = 0;
308 }
309 }
310 pub fn sleeping_count(&self) -> usize {
312 self.sleep_states
313 .iter()
314 .filter(|&&s| s == SleepState::Sleeping)
315 .count()
316 }
317}
318#[derive(Debug, Clone, Copy, Default)]
320pub struct AccumulatedImpulse {
321 pub linear: [f64; 3],
323 pub angular: [f64; 3],
325}
326impl AccumulatedImpulse {
327 pub fn add_linear(&mut self, impulse: [f64; 3]) {
329 for k in 0..3 {
330 self.linear[k] += impulse[k];
331 }
332 }
333 pub fn add_angular(&mut self, impulse: [f64; 3]) {
335 for k in 0..3 {
336 self.angular[k] += impulse[k];
337 }
338 }
339 pub fn apply(&self, state: &mut RigidBodyState) {
341 let im = state.inverse_mass;
342 for k in 0..3 {
343 state.velocity[k] += self.linear[k] * im;
344 state.angular_velocity[k] += self.angular[k] * im;
345 }
346 }
347 pub fn linear_magnitude(&self) -> f64 {
349 self.linear.iter().map(|v| v * v).sum::<f64>().sqrt()
350 }
351 pub fn angular_magnitude(&self) -> f64 {
353 self.angular.iter().map(|v| v * v).sum::<f64>().sqrt()
354 }
355}
356pub struct IntegrateVelocityKernel;
358#[derive(Debug, Clone, Copy)]
360pub struct DistanceConstraint {
361 pub body_a: usize,
363 pub body_b: usize,
365 pub rest_length: f64,
367 pub compliance: f64,
369}
370pub struct SoaRigidBody {
376 pub count: usize,
378 pub pos_x: Vec<f64>,
379 pub pos_y: Vec<f64>,
380 pub pos_z: Vec<f64>,
381 pub vel_x: Vec<f64>,
382 pub vel_y: Vec<f64>,
383 pub vel_z: Vec<f64>,
384 pub quat_x: Vec<f64>,
385 pub quat_y: Vec<f64>,
386 pub quat_z: Vec<f64>,
387 pub quat_w: Vec<f64>,
388 pub omega_x: Vec<f64>,
389 pub omega_y: Vec<f64>,
390 pub omega_z: Vec<f64>,
391 pub inv_mass: Vec<f64>,
392}
393impl SoaRigidBody {
394 pub fn from_slice(states: &[RigidBodyState]) -> Self {
396 let n = states.len();
397 let mut s = Self {
398 count: n,
399 pos_x: Vec::with_capacity(n),
400 pos_y: Vec::with_capacity(n),
401 pos_z: Vec::with_capacity(n),
402 vel_x: Vec::with_capacity(n),
403 vel_y: Vec::with_capacity(n),
404 vel_z: Vec::with_capacity(n),
405 quat_x: Vec::with_capacity(n),
406 quat_y: Vec::with_capacity(n),
407 quat_z: Vec::with_capacity(n),
408 quat_w: Vec::with_capacity(n),
409 omega_x: Vec::with_capacity(n),
410 omega_y: Vec::with_capacity(n),
411 omega_z: Vec::with_capacity(n),
412 inv_mass: Vec::with_capacity(n),
413 };
414 for rb in states {
415 s.pos_x.push(rb.position[0]);
416 s.pos_y.push(rb.position[1]);
417 s.pos_z.push(rb.position[2]);
418 s.vel_x.push(rb.velocity[0]);
419 s.vel_y.push(rb.velocity[1]);
420 s.vel_z.push(rb.velocity[2]);
421 s.quat_x.push(rb.orientation[0]);
422 s.quat_y.push(rb.orientation[1]);
423 s.quat_z.push(rb.orientation[2]);
424 s.quat_w.push(rb.orientation[3]);
425 s.omega_x.push(rb.angular_velocity[0]);
426 s.omega_y.push(rb.angular_velocity[1]);
427 s.omega_z.push(rb.angular_velocity[2]);
428 s.inv_mass.push(rb.inverse_mass);
429 }
430 s
431 }
432 pub fn to_vec(&self) -> Vec<RigidBodyState> {
434 (0..self.count)
435 .map(|i| RigidBodyState {
436 position: [self.pos_x[i], self.pos_y[i], self.pos_z[i]],
437 velocity: [self.vel_x[i], self.vel_y[i], self.vel_z[i]],
438 orientation: [
439 self.quat_x[i],
440 self.quat_y[i],
441 self.quat_z[i],
442 self.quat_w[i],
443 ],
444 angular_velocity: [self.omega_x[i], self.omega_y[i], self.omega_z[i]],
445 inverse_mass: self.inv_mass[i],
446 })
447 .collect()
448 }
449 pub fn integrate_euler(&mut self, forces: &[[f64; 3]], torques: &[[f64; 3]], dt: f64) {
454 for i in 0..self.count {
455 let im = self.inv_mass[i];
456 let ax = forces[i][0] * im;
457 let ay = forces[i][1] * im;
458 let az = forces[i][2] * im;
459 self.vel_x[i] += ax * dt;
460 self.vel_y[i] += ay * dt;
461 self.vel_z[i] += az * dt;
462 self.pos_x[i] += self.vel_x[i] * dt;
463 self.pos_y[i] += self.vel_y[i] * dt;
464 self.pos_z[i] += self.vel_z[i] * dt;
465 let alpha_x = torques[i][0] * im;
466 let alpha_y = torques[i][1] * im;
467 let alpha_z = torques[i][2] * im;
468 self.omega_x[i] += alpha_x * dt;
469 self.omega_y[i] += alpha_y * dt;
470 self.omega_z[i] += alpha_z * dt;
471 let q = [
472 self.quat_x[i],
473 self.quat_y[i],
474 self.quat_z[i],
475 self.quat_w[i],
476 ];
477 let omega = [self.omega_x[i], self.omega_y[i], self.omega_z[i]];
478 let dq = quat_derivative(q, omega);
479 self.quat_x[i] += dq[0] * dt;
480 self.quat_y[i] += dq[1] * dt;
481 self.quat_z[i] += dq[2] * dt;
482 self.quat_w[i] += dq[3] * dt;
483 let qn = quat_normalise([
484 self.quat_x[i],
485 self.quat_y[i],
486 self.quat_z[i],
487 self.quat_w[i],
488 ]);
489 self.quat_x[i] = qn[0];
490 self.quat_y[i] = qn[1];
491 self.quat_z[i] = qn[2];
492 self.quat_w[i] = qn[3];
493 }
494 }
495}
496#[derive(Debug, Clone, Copy)]
498pub struct Aabb {
499 pub min: [f64; 3],
500 pub max: [f64; 3],
501}
502impl Aabb {
503 pub fn from_center(position: [f64; 3], half_extents: [f64; 3]) -> Self {
505 Self {
506 min: [
507 position[0] - half_extents[0],
508 position[1] - half_extents[1],
509 position[2] - half_extents[2],
510 ],
511 max: [
512 position[0] + half_extents[0],
513 position[1] + half_extents[1],
514 position[2] + half_extents[2],
515 ],
516 }
517 }
518 pub fn overlaps(&self, other: &Aabb) -> bool {
520 self.min[0] <= other.max[0]
521 && self.max[0] >= other.min[0]
522 && self.min[1] <= other.max[1]
523 && self.max[1] >= other.min[1]
524 && self.min[2] <= other.max[2]
525 && self.max[2] >= other.min[2]
526 }
527 pub fn expand(&mut self, margin: f64) {
529 for k in 0..3 {
530 self.min[k] -= margin;
531 self.max[k] += margin;
532 }
533 }
534 pub fn volume(&self) -> f64 {
536 (self.max[0] - self.min[0]) * (self.max[1] - self.min[1]) * (self.max[2] - self.min[2])
537 }
538}
539pub struct BroadphaseUpdateKernel;
542impl BroadphaseUpdateKernel {
543 pub fn find_overlapping_pairs(
548 positions: &[[f64; 3]],
549 half_extents: &[[f64; 3]],
550 margin: f64,
551 ) -> Vec<(usize, usize)> {
552 let n = positions.len();
553 let mut aabbs: Vec<Aabb> = Vec::with_capacity(n);
554 for i in 0..n {
555 let mut aabb = Aabb::from_center(positions[i], half_extents[i]);
556 aabb.expand(margin);
557 aabbs.push(aabb);
558 }
559 let mut pairs = Vec::new();
560 for i in 0..n {
561 for j in (i + 1)..n {
562 if aabbs[i].overlaps(&aabbs[j]) {
563 pairs.push((i, j));
564 }
565 }
566 }
567 pairs
568 }
569}
570#[derive(Debug, Clone, Copy)]
572pub struct ContactPoint {
573 pub position: [f64; 3],
575 pub normal: [f64; 3],
577 pub depth: f64,
579 pub body_a: usize,
581 pub body_b: usize,
583}
584pub struct SemiImplicitEulerKernel;
589pub struct QuaternionNormKernel;
594impl QuaternionNormKernel {
595 pub fn normalize_batch(quats: &[[f64; 4]]) -> Vec<[f64; 4]> {
597 quats.iter().map(|&q| quat_normalise(q)).collect()
598 }
599 pub fn normalize_in_place(quats: &mut [[f64; 4]]) {
601 for q in quats.iter_mut() {
602 *q = quat_normalise(*q);
603 }
604 }
605 pub fn count_denormalized(quats: &[[f64; 4]], tol: f64) -> usize {
607 quats
608 .iter()
609 .filter(|&&q| {
610 let len = (q[0] * q[0] + q[1] * q[1] + q[2] * q[2] + q[3] * q[3]).sqrt();
611 (len - 1.0).abs() > tol
612 })
613 .count()
614 }
615}
616pub struct ContactGenerationKernel;
619impl ContactGenerationKernel {
620 pub fn generate_sphere_contacts(
626 positions: &[[f64; 3]],
627 radii: &[f64],
628 pairs: &[(usize, usize)],
629 ) -> Vec<ContactPoint> {
630 let mut contacts = Vec::new();
631 for &(a, b) in pairs {
632 let dx = [
633 positions[b][0] - positions[a][0],
634 positions[b][1] - positions[a][1],
635 positions[b][2] - positions[a][2],
636 ];
637 let dist_sq = dx[0] * dx[0] + dx[1] * dx[1] + dx[2] * dx[2];
638 let sum_r = radii[a] + radii[b];
639 if dist_sq < sum_r * sum_r {
640 let dist = dist_sq.sqrt();
641 let depth = sum_r - dist;
642 let normal = if dist > 1e-14 {
643 [dx[0] / dist, dx[1] / dist, dx[2] / dist]
644 } else {
645 [0.0, 1.0, 0.0]
646 };
647 let contact_pos = [
648 positions[a][0] + normal[0] * radii[a],
649 positions[a][1] + normal[1] * radii[a],
650 positions[a][2] + normal[2] * radii[a],
651 ];
652 contacts.push(ContactPoint {
653 position: contact_pos,
654 normal,
655 depth,
656 body_a: a,
657 body_b: b,
658 });
659 }
660 }
661 contacts
662 }
663 pub fn resolve_contacts(
667 positions: &mut [[f64; 3]],
668 velocities: &mut [[f64; 3]],
669 inv_masses: &[f64],
670 contacts: &[ContactPoint],
671 restitution: f64,
672 ) {
673 for c in contacts {
674 let a = c.body_a;
675 let b = c.body_b;
676 let w_a = inv_masses[a];
677 let w_b = inv_masses[b];
678 let w_total = w_a + w_b;
679 if w_total < 1e-30 {
680 continue;
681 }
682 let correction = c.depth / w_total;
683 for k in 0..3 {
684 positions[a][k] -= w_a * correction * c.normal[k];
685 positions[b][k] += w_b * correction * c.normal[k];
686 }
687 let rel_vel = [
688 velocities[b][0] - velocities[a][0],
689 velocities[b][1] - velocities[a][1],
690 velocities[b][2] - velocities[a][2],
691 ];
692 let vel_along_normal =
693 rel_vel[0] * c.normal[0] + rel_vel[1] * c.normal[1] + rel_vel[2] * c.normal[2];
694 if vel_along_normal < 0.0 {
695 let j = -(1.0 + restitution) * vel_along_normal / w_total;
696 for k in 0..3 {
697 velocities[a][k] -= w_a * j * c.normal[k];
698 velocities[b][k] += w_b * j * c.normal[k];
699 }
700 }
701 }
702 }
703}