1use phyz_math::DVec;
65use phyz_model::{JointType, Model};
66use tang::{Mat3, Quat, Scalar, SpatialInertia, SpatialMat, SpatialTransform, SpatialVec, Vec3};
67
68pub const N_INERTIA_PARAMS: usize = 10;
74
75pub fn inertia_params(si: &SpatialInertia<f64>) -> [f64; N_INERTIA_PARAMS] {
77 [
78 si.mass,
79 si.com.x,
80 si.com.y,
81 si.com.z,
82 si.inertia.get(0, 0),
83 si.inertia.get(1, 1),
84 si.inertia.get(2, 2),
85 si.inertia.get(0, 1),
86 si.inertia.get(0, 2),
87 si.inertia.get(1, 2),
88 ]
89}
90
91pub fn inertia_from_params<T: Scalar>(p: &[T; N_INERTIA_PARAMS]) -> SpatialInertia<T> {
96 SpatialInertia::new(
97 p[0],
98 Vec3::new(p[1], p[2], p[3]),
99 Mat3::new(p[4], p[7], p[8], p[7], p[5], p[9], p[8], p[9], p[6]),
100 )
101}
102
103#[derive(Debug, Clone, Copy)]
105pub struct GroundContact {
106 pub height: f64,
108 pub stiffness: f64,
110 pub damping: f64,
112}
113
114#[derive(Debug, Clone)]
117pub struct CollisionMesh {
118 pub body: usize,
120 pub vertices: Vec<Vec3<f64>>,
122}
123
124pub(crate) fn lift_vec3<T: Scalar>(v: Vec3<f64>) -> Vec3<T> {
129 Vec3::new(T::from_f64(v.x), T::from_f64(v.y), T::from_f64(v.z))
130}
131
132fn lift_mat3<T: Scalar>(m: &Mat3<f64>) -> Mat3<T> {
133 Mat3::from_cols(
134 lift_vec3(m.col(0)),
135 lift_vec3(m.col(1)),
136 lift_vec3(m.col(2)),
137 )
138}
139
140fn lift_xform<T: Scalar>(xf: &SpatialTransform<f64>) -> SpatialTransform<T> {
141 SpatialTransform::new(lift_mat3(&xf.rot), lift_vec3(xf.pos))
142}
143
144pub(crate) fn lift_inertia<T: Scalar>(si: &SpatialInertia<f64>) -> SpatialInertia<T> {
146 SpatialInertia::new(
147 T::from_f64(si.mass),
148 lift_vec3(si.com),
149 lift_mat3(&si.inertia),
150 )
151}
152
153const MAX_DOF: usize = 6;
159
160pub fn joint_nq(joint_type: JointType) -> usize {
164 match joint_type {
165 JointType::Revolute | JointType::Hinge | JointType::Prismatic | JointType::Slide => 1,
166 JointType::Spherical | JointType::Ball => 4,
167 JointType::Free => 7,
168 JointType::Fixed => 0,
169 }
170}
171
172#[derive(Debug, Clone, PartialEq, Eq)]
180pub struct DofLayout {
181 pub nq: usize,
183 pub nv: usize,
185 pub q_offsets: Vec<usize>,
187 pub v_offsets: Vec<usize>,
189}
190
191impl DofLayout {
192 pub fn of(model: &Model) -> Self {
194 let mut nq = 0;
195 let mut nv = 0;
196 let mut q_offsets = Vec::with_capacity(model.joints.len());
197 let mut v_offsets = Vec::with_capacity(model.joints.len());
198 for joint in &model.joints {
199 q_offsets.push(nq);
200 v_offsets.push(nv);
201 nq += joint_nq(joint.joint_type);
202 nv += joint.ndof();
203 }
204 Self {
205 nq,
206 nv,
207 q_offsets,
208 v_offsets,
209 }
210 }
211
212 pub fn neutral_q(&self, model: &Model) -> Vec<f64> {
216 let mut q = vec![0.0; self.nq];
217 for (j, joint) in model.joints.iter().enumerate() {
218 let qi = self.q_offsets[j];
219 match joint.joint_type {
220 JointType::Spherical | JointType::Ball => q[qi] = 1.0,
221 JointType::Free => q[qi + 3] = 1.0,
222 _ => {}
223 }
224 }
225 q
226 }
227}
228
229fn joint_transform<T: Scalar>(
236 joint_type: JointType,
237 axis: &Vec3<T>,
238 q: &[T],
239) -> SpatialTransform<T> {
240 match joint_type {
241 JointType::Revolute | JointType::Hinge => {
242 let (s, c) = (-q[0]).sin_cos();
245 let ax = tang::skew(axis);
246 let rot = Mat3::identity() + ax * s + ax.mul_mat(&ax) * (T::ONE - c);
247 SpatialTransform::new(rot, Vec3::zero())
248 }
249 JointType::Prismatic | JointType::Slide => {
250 SpatialTransform::new(Mat3::identity(), *axis * q[0])
251 }
252 JointType::Spherical | JointType::Ball => {
253 let rot = quat_at(&q[0..4]).normalize().to_matrix();
254 SpatialTransform::new(rot, Vec3::zero())
255 }
256 JointType::Free => {
257 let rot = quat_at(&q[3..7]).normalize().to_matrix();
258 SpatialTransform::new(rot, Vec3::new(q[0], q[1], q[2]))
259 }
260 JointType::Fixed => SpatialTransform::identity(),
261 }
262}
263
264fn quat_at<T: Scalar>(q: &[T]) -> Quat<T> {
266 Quat::new(q[0], q[1], q[2], q[3])
267}
268
269fn motion_subspace<T: Scalar>(joint_type: JointType, axis: &Vec3<T>) -> SpatialVec<T> {
271 match joint_type {
272 JointType::Revolute | JointType::Hinge => SpatialVec::new(*axis, Vec3::zero()),
273 JointType::Prismatic | JointType::Slide => SpatialVec::new(Vec3::zero(), *axis),
274 _ => unreachable!("motion_subspace is the 1-DOF path"),
275 }
276}
277
278fn unit<T: Scalar>(k: usize) -> Vec3<T> {
280 let mut e = Vec3::zero();
281 match k {
282 0 => e.x = T::ONE,
283 1 => e.y = T::ONE,
284 _ => e.z = T::ONE,
285 }
286 e
287}
288
289fn motion_subspace_cols<T: Scalar>(
292 joint_type: JointType,
293 axis: &Vec3<T>,
294) -> [SpatialVec<T>; MAX_DOF] {
295 let mut s = [SpatialVec::zero(); MAX_DOF];
296 match joint_type {
297 JointType::Revolute | JointType::Hinge | JointType::Prismatic | JointType::Slide => {
298 s[0] = motion_subspace(joint_type, axis);
299 }
300 JointType::Spherical | JointType::Ball => {
301 for (k, s_k) in s.iter_mut().enumerate().take(3) {
302 *s_k = SpatialVec::new(unit(k), Vec3::zero());
303 }
304 }
305 JointType::Free => {
306 for k in 0..3 {
307 s[k] = SpatialVec::new(unit(k), Vec3::zero());
308 s[3 + k] = SpatialVec::new(Vec3::zero(), unit(k));
309 }
310 }
311 JointType::Fixed => {}
312 }
313 s
314}
315
316fn joint_velocity<T: Scalar>(
318 joint_type: JointType,
319 axis: &Vec3<T>,
320 v: &[T],
321 ndof: usize,
322) -> SpatialVec<T> {
323 if ndof == 1 {
324 let s = motion_subspace(joint_type, axis);
325 return SpatialVec::new(s.angular * v[0], s.linear * v[0]);
326 }
327 let s = motion_subspace_cols(joint_type, axis);
328 let mut out = SpatialVec::zero();
329 for k in 0..ndof {
330 out = out + SpatialVec::new(s[k].angular * v[k], s[k].linear * v[k]);
331 }
332 out
333}
334
335fn solve_in_place<T: Scalar>(a: &mut [T], n: usize, b: &mut [T], m: usize) -> bool {
344 for col in 0..n {
345 let mut piv = col;
346 let mut best = a[col * n + col].to_f64().abs();
347 for row in (col + 1)..n {
348 let mag = a[row * n + col].to_f64().abs();
349 if mag > best {
350 best = mag;
351 piv = row;
352 }
353 }
354 if best < 1e-20 {
355 return false;
356 }
357 if piv != col {
358 for k in 0..n {
359 a.swap(col * n + k, piv * n + k);
360 }
361 for k in 0..m {
362 b.swap(col * m + k, piv * m + k);
363 }
364 }
365 let inv = a[col * n + col].recip();
366 for row in (col + 1)..n {
367 let f = a[row * n + col] * inv;
368 if f.to_f64() == 0.0 {
369 continue;
370 }
371 for k in col..n {
372 let t = f * a[col * n + k];
373 a[row * n + k] -= t;
374 }
375 for k in 0..m {
376 let t = f * b[col * m + k];
377 b[row * m + k] -= t;
378 }
379 }
380 }
381 for col in (0..n).rev() {
383 let inv = a[col * n + col].recip();
384 for k in 0..m {
385 let mut acc = b[col * m + k];
386 for j in (col + 1)..n {
387 acc -= a[col * n + j] * b[j * m + k];
388 }
389 b[col * m + k] = acc * inv;
390 }
391 }
392 true
393}
394
395pub(crate) fn fk_generic<T: Scalar>(
403 model: &Model,
404 layout: &DofLayout,
405 q: &[T],
406 v: &[T],
407) -> (Vec<SpatialTransform<T>>, Vec<SpatialVec<T>>) {
408 let nb = model.nbodies();
409 let mut xforms: Vec<SpatialTransform<T>> = vec![SpatialTransform::identity(); nb];
410 let mut vels: Vec<SpatialVec<T>> = vec![SpatialVec::zero(); nb];
411
412 for i in 0..nb {
413 let body = &model.bodies[i];
414 let joint = &model.joints[body.joint_idx];
415 let q_idx = layout.q_offsets[body.joint_idx];
416 let v_idx = layout.v_offsets[body.joint_idx];
417 let ndof = joint.ndof();
418 let axis = lift_vec3::<T>(joint.axis);
419
420 let x_joint = if ndof == 0 {
421 SpatialTransform::identity()
422 } else {
423 joint_transform(joint.joint_type, &axis, &q[q_idx..])
424 };
425 let x_tree = x_joint.compose(&lift_xform(&joint.parent_to_joint));
426
427 let v_joint = if ndof == 0 {
428 SpatialVec::zero()
429 } else {
430 joint_velocity(joint.joint_type, &axis, &v[v_idx..], ndof)
431 };
432
433 if body.parent < 0 {
434 xforms[i] = x_tree;
435 vels[i] = v_joint;
436 } else {
437 let pi = body.parent as usize;
438 xforms[i] = x_tree.compose(&xforms[pi]);
439 vels[i] = x_tree.apply_motion(&vels[pi]) + v_joint;
440 }
441 }
442
443 (xforms, vels)
444}
445
446pub(crate) fn vertex_wrench<T: Scalar>(
458 gc: &GroundContact,
459 x_b: Vec3<T>,
460 xform: &SpatialTransform<T>,
461 twist: &SpatialVec<T>,
462) -> SpatialVec<T> {
463 let e = &xform.rot; let x_w = xform.pos + e.transpose().mul_vec(x_b);
465 let depth = T::from_f64(gc.height) - x_w.z;
466
467 let v_b = twist.linear + twist.angular.cross(x_b);
469 let v_w = e.transpose().mul_vec(v_b);
470
471 let raw = T::from_f64(gc.stiffness) * depth - T::from_f64(gc.damping) * v_w.z;
476 let f_mag = T::select(depth, raw.max(T::ZERO), T::ZERO);
477
478 let f_w = Vec3::new(T::ZERO, T::ZERO, f_mag);
479 let f_b = e.mul_vec(f_w);
480 SpatialVec::new(x_b.cross(f_b), f_b)
481}
482
483fn contact_wrenches<T: Scalar>(
486 gc: &GroundContact,
487 meshes: &[CollisionMesh],
488 xforms: &[SpatialTransform<T>],
489 vels: &[SpatialVec<T>],
490 out: &mut [SpatialVec<T>],
491) {
492 for mesh in meshes {
493 let b = mesh.body;
494 for v in &mesh.vertices {
495 out[b] = out[b] + vertex_wrench(gc, lift_vec3(*v), &xforms[b], &vels[b]);
496 }
497 }
498}
499
500fn implicit_damping<T: Scalar>(model: &Model, joint: &phyz_model::Joint) -> T {
514 T::from_f64(model.dt * joint.damping)
515}
516
517#[allow(clippy::too_many_arguments)]
526pub(crate) fn aba_generic<T: Scalar>(
527 model: &Model,
528 layout: &DofLayout,
529 inertias: &[SpatialInertia<T>],
530 q: &[T],
531 v: &[T],
532 ctrl: &[T],
533 ext: Option<&[SpatialVec<T>]>,
534) -> Vec<T> {
535 let nb = model.nbodies();
536 let mut qdd = vec![T::ZERO; layout.nv];
537
538 let mut x_tree: Vec<SpatialTransform<T>> = vec![SpatialTransform::identity(); nb];
539 let mut vel: Vec<SpatialVec<T>> = vec![SpatialVec::zero(); nb];
540 let mut c_bias: Vec<SpatialVec<T>> = vec![SpatialVec::zero(); nb];
541 let mut p_a: Vec<SpatialVec<T>> = vec![SpatialVec::zero(); nb];
542 let mut i_a: Vec<SpatialMat<T>> = vec![SpatialMat::zero(); nb];
543
544 let a0: SpatialVec<T> = SpatialVec::new(Vec3::zero(), -lift_vec3(model.gravity));
546
547 for i in 0..nb {
549 let body = &model.bodies[i];
550 let joint = &model.joints[body.joint_idx];
551 let q_idx = layout.q_offsets[body.joint_idx];
552 let v_idx = layout.v_offsets[body.joint_idx];
553 let ndof = joint.ndof();
554 let axis = lift_vec3::<T>(joint.axis);
555
556 let x_joint = if ndof == 0 {
557 SpatialTransform::identity()
558 } else {
559 joint_transform(joint.joint_type, &axis, &q[q_idx..])
560 };
561 x_tree[i] = x_joint.compose(&lift_xform(&joint.parent_to_joint));
562
563 let v_joint = if ndof == 0 {
564 SpatialVec::zero()
565 } else {
566 joint_velocity(joint.joint_type, &axis, &v[v_idx..], ndof)
567 };
568
569 if body.parent < 0 {
570 vel[i] = v_joint;
571 c_bias[i] = SpatialVec::zero();
572 } else {
573 let pi = body.parent as usize;
574 vel[i] = x_tree[i].apply_motion(&vel[pi]) + v_joint;
575 c_bias[i] = vel[i].cross_motion(&v_joint);
576 }
577
578 i_a[i] = inertias[i].to_matrix();
579 p_a[i] = vel[i].cross_force(&i_a[i].mul_vec(&vel[i]));
580 if let Some(ext) = ext {
581 p_a[i] = p_a[i] - ext[i];
582 }
583 }
584
585 for i in (0..nb).rev() {
587 let body = &model.bodies[i];
588 let joint = &model.joints[body.joint_idx];
589 let v_idx = layout.v_offsets[body.joint_idx];
590 let ndof = joint.ndof();
591
592 if ndof == 0 {
593 if body.parent >= 0 {
594 let pi = body.parent as usize;
595 let x_mot = x_tree[i].to_motion_matrix();
596 i_a[pi] = i_a[pi] + x_mot.transpose().mul_mat(&i_a[i]).mul_mat(&x_mot);
597 p_a[pi] = p_a[pi] + x_tree[i].inv_apply_force(&p_a[i]);
598 }
599 continue;
600 }
601
602 let axis = lift_vec3::<T>(joint.axis);
603
604 if ndof > 1 {
605 let s = motion_subspace_cols(joint.joint_type, &axis);
608 let ia = i_a[i];
609 let mut u_cols = [SpatialVec::<T>::zero(); MAX_DOF];
610 for k in 0..ndof {
611 u_cols[k] = ia.mul_vec(&s[k]);
612 }
613
614 let mut d = vec![T::ZERO; ndof * ndof];
615 for k in 0..ndof {
616 for l in 0..ndof {
617 d[k * ndof + l] = s[k].dot(&u_cols[l]);
618 }
619 d[k * ndof + k] += implicit_damping::<T>(model, joint);
620 }
621 let mut rhs = vec![T::ZERO; ndof * 7];
623 for k in 0..ndof {
624 rhs[k * 7] =
625 ctrl[v_idx + k] - T::from_f64(joint.damping) * v[v_idx + k] - s[k].dot(&p_a[i]);
626 let uk = u_cols[k].as_array();
627 for c in 0..6 {
628 rhs[k * 7 + 1 + c] = uk[c];
629 }
630 }
631 if !solve_in_place(&mut d, ndof, &mut rhs, 7) {
632 continue;
633 }
634
635 if body.parent >= 0 {
636 let pi = body.parent as usize;
637 let mut ia_new = ia;
639 for k in 0..ndof {
640 let y_k = SpatialVec::new(
641 Vec3::new(rhs[k * 7 + 1], rhs[k * 7 + 2], rhs[k * 7 + 3]),
642 Vec3::new(rhs[k * 7 + 4], rhs[k * 7 + 5], rhs[k * 7 + 6]),
643 );
644 ia_new = ia_new - outer_product_6(&u_cols[k], &y_k);
645 }
646 let mut p_new = p_a[i] + ia_new.mul_vec(&c_bias[i]);
648 for k in 0..ndof {
649 p_new = p_new + u_cols[k] * rhs[k * 7];
650 }
651
652 let x_mot = x_tree[i].to_motion_matrix();
653 i_a[pi] = i_a[pi] + x_mot.transpose().mul_mat(&ia_new).mul_mat(&x_mot);
654 p_a[pi] = p_a[pi] + x_tree[i].inv_apply_force(&p_new);
655 }
656 continue;
657 }
658
659 let s_i = motion_subspace(joint.joint_type, &axis);
660 let tau_i = ctrl[v_idx] - T::from_f64(joint.damping) * v[v_idx];
661
662 let ia = &i_a[i];
663 let u_i = tau_i - s_i.dot(&p_a[i]);
664 let d_i = s_i.dot(&ia.mul_vec(&s_i)) + implicit_damping::<T>(model, joint);
665
666 if d_i.to_f64().abs() < 1e-20 {
668 continue;
669 }
670
671 let ia_s = ia.mul_vec(&s_i);
672 if body.parent >= 0 {
673 let pi = body.parent as usize;
674 let d_inv = d_i.recip();
675 let outer = outer_product_6(&ia_s, &ia_s);
676 let ia_new = *ia - outer * d_inv;
677 let p_new = p_a[i] + ia_new.mul_vec(&c_bias[i]) + ia_s * (u_i * d_inv);
678
679 let x_mot = x_tree[i].to_motion_matrix();
680 i_a[pi] = i_a[pi] + x_mot.transpose().mul_mat(&ia_new).mul_mat(&x_mot);
681 p_a[pi] = p_a[pi] + x_tree[i].inv_apply_force(&p_new);
682 }
683 }
684
685 let mut acc: Vec<SpatialVec<T>> = vec![SpatialVec::zero(); nb];
687 for i in 0..nb {
688 let body = &model.bodies[i];
689 let joint = &model.joints[body.joint_idx];
690 let v_idx = layout.v_offsets[body.joint_idx];
691 let ndof = joint.ndof();
692
693 let a_parent = if body.parent < 0 {
694 x_tree[i].apply_motion(&a0)
695 } else {
696 x_tree[i].apply_motion(&acc[body.parent as usize])
697 };
698
699 if ndof == 0 {
700 acc[i] = a_parent + c_bias[i];
701 continue;
702 }
703
704 let axis = lift_vec3::<T>(joint.axis);
705
706 if ndof > 1 {
707 let s = motion_subspace_cols(joint.joint_type, &axis);
709 let ia = i_a[i];
710 let a_total = a_parent + c_bias[i];
711 let ia_a = ia.mul_vec(&a_total);
712
713 let mut d = vec![T::ZERO; ndof * ndof];
714 let mut rhs = vec![T::ZERO; ndof];
715 for k in 0..ndof {
716 for l in 0..ndof {
717 d[k * ndof + l] = s[k].dot(&ia.mul_vec(&s[l]));
718 }
719 d[k * ndof + k] += implicit_damping::<T>(model, joint);
720 rhs[k] = ctrl[v_idx + k]
721 - T::from_f64(joint.damping) * v[v_idx + k]
722 - s[k].dot(&p_a[i])
723 - s[k].dot(&ia_a);
724 }
725 if !solve_in_place(&mut d, ndof, &mut rhs, 1) {
726 acc[i] = a_total;
727 continue;
728 }
729 let mut s_qdd = SpatialVec::zero();
730 for k in 0..ndof {
731 qdd[v_idx + k] = rhs[k];
732 s_qdd = s_qdd + SpatialVec::new(s[k].angular * rhs[k], s[k].linear * rhs[k]);
733 }
734 acc[i] = a_total + s_qdd;
735 continue;
736 }
737
738 let s_i = motion_subspace(joint.joint_type, &axis);
739 let ia = &i_a[i];
740 let d_i = s_i.dot(&ia.mul_vec(&s_i)) + implicit_damping::<T>(model, joint);
741 if d_i.to_f64().abs() < 1e-20 {
742 acc[i] = a_parent + c_bias[i];
743 continue;
744 }
745
746 let tau_i = ctrl[v_idx] - T::from_f64(joint.damping) * v[v_idx];
747 let u_i = tau_i - s_i.dot(&p_a[i]);
748 let qdd_i = (u_i - ia.mul_vec(&(a_parent + c_bias[i])).dot(&s_i)) * d_i.recip();
749 qdd[v_idx] = qdd_i;
750 acc[i] = a_parent + c_bias[i] + SpatialVec::new(s_i.angular * qdd_i, s_i.linear * qdd_i);
751 }
752
753 qdd
754}
755
756fn outer_product_6<T: Scalar>(a: &SpatialVec<T>, b: &SpatialVec<T>) -> SpatialMat<T> {
757 fn v3_outer<T: Scalar>(a: Vec3<T>, b: Vec3<T>) -> Mat3<T> {
758 Mat3::new(
759 a.x * b.x,
760 a.x * b.y,
761 a.x * b.z,
762 a.y * b.x,
763 a.y * b.y,
764 a.y * b.z,
765 a.z * b.x,
766 a.z * b.y,
767 a.z * b.z,
768 )
769 }
770 SpatialMat::new(
771 v3_outer(a.angular, b.angular),
772 v3_outer(a.angular, b.linear),
773 v3_outer(a.linear, b.angular),
774 v3_outer(a.linear, b.linear),
775 )
776}
777
778pub(crate) fn nominal_motion(
787 model: &Model,
788 layout: &DofLayout,
789 q: &[f64],
790 v: &[f64],
791 qdd: &[f64],
792) -> (Vec<SpatialVec<f64>>, Vec<SpatialVec<f64>>) {
793 let nb = model.nbodies();
794 let mut vel = vec![SpatialVec::<f64>::zero(); nb];
795 let mut c_bias = vec![SpatialVec::<f64>::zero(); nb];
796 let mut acc = vec![SpatialVec::<f64>::zero(); nb];
797 let mut x_tree = vec![SpatialTransform::<f64>::identity(); nb];
798
799 let a0 = SpatialVec::new(Vec3::zero(), -model.gravity);
800
801 for i in 0..nb {
802 let body = &model.bodies[i];
803 let joint = &model.joints[body.joint_idx];
804 let q_idx = layout.q_offsets[body.joint_idx];
805 let v_idx = layout.v_offsets[body.joint_idx];
806 let ndof = joint.ndof();
807
808 let x_joint = if ndof == 0 {
809 SpatialTransform::identity()
810 } else {
811 joint_transform(joint.joint_type, &joint.axis, &q[q_idx..])
812 };
813 x_tree[i] = x_joint.compose(&joint.parent_to_joint);
814
815 let v_joint = if ndof == 0 {
816 SpatialVec::zero()
817 } else {
818 joint_velocity(joint.joint_type, &joint.axis, &v[v_idx..], ndof)
819 };
820
821 if body.parent < 0 {
822 vel[i] = v_joint;
823 } else {
824 let pi = body.parent as usize;
825 vel[i] = x_tree[i].apply_motion(&vel[pi]) + v_joint;
826 c_bias[i] = vel[i].cross_motion(&v_joint);
827 }
828 }
829
830 for i in 0..nb {
831 let body = &model.bodies[i];
832 let joint = &model.joints[body.joint_idx];
833 let v_idx = layout.v_offsets[body.joint_idx];
834 let ndof = joint.ndof();
835
836 let a_parent = if body.parent < 0 {
837 x_tree[i].apply_motion(&a0)
838 } else {
839 x_tree[i].apply_motion(&acc[body.parent as usize])
840 };
841
842 acc[i] = a_parent + c_bias[i];
843 if ndof > 0 {
844 acc[i] = acc[i] + joint_velocity(joint.joint_type, &joint.axis, &qdd[v_idx..], ndof);
847 }
848 }
849
850 (vel, acc)
851}
852
853pub(crate) fn config_update_generic<T: Scalar>(
867 model: &Model,
868 layout: &DofLayout,
869 q: &[T],
870 v_next: &[T],
871 dt: T,
872) -> Vec<T> {
873 let mut out = q.to_vec();
874 for (j, joint) in model.joints.iter().enumerate() {
875 let qi = layout.q_offsets[j];
876 let vi = layout.v_offsets[j];
877 match joint.joint_type {
878 JointType::Revolute | JointType::Hinge | JointType::Prismatic | JointType::Slide => {
879 out[qi] = q[qi] + dt * v_next[vi];
880 }
881 JointType::Spherical | JointType::Ball => {
882 let p = quat_at(&q[qi..qi + 4]);
883 let omega = Vec3::new(v_next[vi], v_next[vi + 1], v_next[vi + 2]);
884 write_quat(&mut out[qi..qi + 4], &rotate_step(&p, omega, dt));
885 }
886 JointType::Free => {
887 let p = quat_at(&q[qi + 3..qi + 7]);
888 let e = p.normalize().to_matrix();
889 let v_lin = Vec3::new(v_next[vi + 3], v_next[vi + 4], v_next[vi + 5]);
890 let dr = e.transpose().mul_vec(v_lin) * dt;
891 out[qi] = q[qi] + dr.x;
892 out[qi + 1] = q[qi + 1] + dr.y;
893 out[qi + 2] = q[qi + 2] + dr.z;
894
895 let omega = Vec3::new(v_next[vi], v_next[vi + 1], v_next[vi + 2]);
896 write_quat(&mut out[qi + 3..qi + 7], &rotate_step(&p, omega, dt));
897 }
898 JointType::Fixed => {}
899 }
900 }
901 out
902}
903
904fn rotate_step<T: Scalar>(p: &Quat<T>, omega: Vec3<T>, dt: T) -> Quat<T> {
907 Quat::exp(&(omega * -dt)).mul(p).normalize()
908}
909
910fn write_quat<T: Scalar>(out: &mut [T], p: &Quat<T>) {
911 out[0] = p.w;
912 out[1] = p.v.x;
913 out[2] = p.v.y;
914 out[3] = p.v.z;
915}
916
917#[allow(clippy::too_many_arguments)]
929pub(crate) fn step_generic<T: Scalar>(
930 model: &Model,
931 layout: &DofLayout,
932 inertias: &[SpatialInertia<T>],
933 contact: Option<(&GroundContact, &[CollisionMesh])>,
934 ext: Option<&[SpatialVec<T>]>,
935 q: &[T],
936 v: &[T],
937 ctrl: &[T],
938) -> (Vec<T>, Vec<T>, Vec<T>) {
939 let nb = model.nbodies();
940 let dt = T::from_f64(model.dt);
941
942 let total_ext: Option<Vec<SpatialVec<T>>> = if contact.is_some() || ext.is_some() {
943 let mut w: Vec<SpatialVec<T>> = match ext {
944 Some(e) => e.to_vec(),
945 None => vec![SpatialVec::zero(); nb],
946 };
947 if let Some((gc, meshes)) = contact {
948 let (xforms, vels) = fk_generic(model, layout, q, v);
949 contact_wrenches(gc, meshes, &xforms, &vels, &mut w);
950 }
951 Some(w)
952 } else {
953 None
954 };
955
956 let qdd = aba_generic(model, layout, inertias, q, v, ctrl, total_ext.as_deref());
957
958 let v_next: Vec<T> = v.iter().zip(&qdd).map(|(&vi, &ai)| vi + dt * ai).collect();
959 let q_next = config_update_generic(model, layout, q, &v_next, dt);
960
961 (q_next, v_next, qdd)
962}
963
964pub(crate) fn validate_and_params(model: &Model) -> (DofLayout, Vec<[f64; N_INERTIA_PARAMS]>) {
967 let layout = DofLayout::of(model);
968 let params = model
969 .bodies
970 .iter()
971 .map(|b| inertia_params(&b.inertia))
972 .collect();
973 (layout, params)
974}
975
976pub(crate) type RolloutTrace = (Vec<(Vec<f64>, Vec<f64>)>, Vec<Vec<f64>>);
978
979pub(crate) fn rollout_states(
986 model: &Model,
987 layout: &DofLayout,
988 contact: Option<(&GroundContact, &[CollisionMesh])>,
989 q0: &[f64],
990 v0: &[f64],
991 ctrl: &dyn Fn(usize) -> DVec,
992 steps: usize,
993) -> RolloutTrace {
994 let inertias: Vec<SpatialInertia<f64>> = model.bodies.iter().map(|b| b.inertia).collect();
995 let mut states = Vec::with_capacity(steps + 1);
996 let mut accels = Vec::with_capacity(steps);
997 let mut q = q0.to_vec();
998 let mut v = v0.to_vec();
999 states.push((q.clone(), v.clone()));
1000 for t in 0..steps {
1001 let u = ctrl(t);
1002 let (qn, vn, qdd) = step_generic::<f64>(
1003 model,
1004 layout,
1005 &inertias,
1006 contact,
1007 None,
1008 &q,
1009 &v,
1010 u.as_slice(),
1011 );
1012 q = qn;
1013 v = vn;
1014 states.push((q.clone(), v.clone()));
1015 accels.push(qdd);
1016 }
1017 (states, accels)
1018}