1use crate::joint::Joint;
7use crate::math::{Fix128, QuatFix, Vec3Fix};
8use crate::solver::RigidBody;
9
10#[derive(Clone, Copy, Debug, PartialEq, Eq)]
12pub enum MotorMode {
13 Off,
15 Position,
17 Velocity,
19}
20
21#[derive(Clone, Copy, Debug, PartialEq, Eq)]
23pub struct PdController {
24 pub kp: Fix128,
26 pub kd: Fix128,
28 pub max_force: Fix128,
30 pub target_position: Fix128,
32 pub target_velocity: Fix128,
34 pub mode: MotorMode,
36}
37
38impl PdController {
39 #[inline]
41 #[must_use]
42 pub const fn new(kp: Fix128, kd: Fix128, max_force: Fix128) -> Self {
43 Self {
44 kp,
45 kd,
46 max_force,
47 target_position: Fix128::ZERO,
48 target_velocity: Fix128::ZERO,
49 mode: MotorMode::Off,
50 }
51 }
52
53 pub fn set_position_target(&mut self, target: Fix128) {
55 self.target_position = target;
56 self.mode = MotorMode::Position;
57 }
58
59 pub fn set_velocity_target(&mut self, target: Fix128) {
61 self.target_velocity = target;
62 self.mode = MotorMode::Velocity;
63 }
64
65 pub fn disable(&mut self) {
67 self.mode = MotorMode::Off;
68 }
69
70 #[must_use]
74 pub fn compute(&self, current_position: Fix128, current_velocity: Fix128) -> Fix128 {
75 match self.mode {
76 MotorMode::Off => Fix128::ZERO,
77 MotorMode::Position => {
78 let error = self.target_position - current_position;
79 let vel_error = self.target_velocity - current_velocity;
80 let output = self.kp * error + self.kd * vel_error;
81 clamp(output, -self.max_force, self.max_force)
82 }
83 MotorMode::Velocity => {
84 let vel_error = self.target_velocity - current_velocity;
85 let output = self.kp * vel_error;
86 clamp(output, -self.max_force, self.max_force)
87 }
88 }
89 }
90}
91
92impl Default for PdController {
93 fn default() -> Self {
94 Self::new(
95 Fix128::from_int(100), Fix128::from_int(10), Fix128::from_int(1000), )
99 }
100}
101
102#[derive(Clone, Copy, Debug, PartialEq, Eq)]
104pub struct JointMotor {
105 pub joint_index: usize,
107 pub controller: PdController,
109}
110
111impl JointMotor {
112 #[must_use]
114 pub const fn new(joint_index: usize, controller: PdController) -> Self {
115 Self {
116 joint_index,
117 controller,
118 }
119 }
120}
121
122#[derive(Clone, Copy, Debug, PartialEq, Eq)]
124pub struct PdController3D {
125 pub kp: Vec3Fix,
127 pub kd: Vec3Fix,
129 pub max_torque: Fix128,
131 pub target_rotation: QuatFix,
133 pub target_angular_velocity: Vec3Fix,
135 pub mode: MotorMode,
137}
138
139impl PdController3D {
140 #[inline]
142 #[must_use]
143 pub const fn new(kp: Vec3Fix, kd: Vec3Fix, max_torque: Fix128) -> Self {
144 Self {
145 kp,
146 kd,
147 max_torque,
148 target_rotation: QuatFix::IDENTITY,
149 target_angular_velocity: Vec3Fix::ZERO,
150 mode: MotorMode::Off,
151 }
152 }
153
154 pub fn set_rotation_target(&mut self, target: QuatFix) {
156 self.target_rotation = target;
157 self.mode = MotorMode::Position;
158 }
159
160 #[must_use]
162 pub fn compute_torque(
163 &self,
164 current_rotation: QuatFix,
165 current_angular_velocity: Vec3Fix,
166 ) -> Vec3Fix {
167 match self.mode {
168 MotorMode::Off => Vec3Fix::ZERO,
169 MotorMode::Position => {
170 let error_quat =
172 shortest_arc(self.target_rotation.mul(current_rotation.conjugate()));
173 let error_axis = Vec3Fix::new(error_quat.x, error_quat.y, error_quat.z);
174 let two = Fix128::from_int(2);
175 let error_scaled =
176 Vec3Fix::new(error_axis.x * two, error_axis.y * two, error_axis.z * two);
177
178 let vel_error = self.target_angular_velocity - current_angular_velocity;
180 let torque = Vec3Fix::new(
181 self.kp.x * error_scaled.x + self.kd.x * vel_error.x,
182 self.kp.y * error_scaled.y + self.kd.y * vel_error.y,
183 self.kp.z * error_scaled.z + self.kd.z * vel_error.z,
184 );
185
186 let mag = torque.length();
188 if mag > self.max_torque && !mag.is_zero() {
189 torque * (self.max_torque / mag)
190 } else {
191 torque
192 }
193 }
194 MotorMode::Velocity => {
195 let vel_error = self.target_angular_velocity - current_angular_velocity;
196 let torque = Vec3Fix::new(
197 self.kp.x * vel_error.x,
198 self.kp.y * vel_error.y,
199 self.kp.z * vel_error.z,
200 );
201
202 let mag = torque.length();
203 if mag > self.max_torque && !mag.is_zero() {
204 torque * (self.max_torque / mag)
205 } else {
206 torque
207 }
208 }
209 }
210 }
211}
212
213pub fn apply_motors(motors: &[JointMotor], joints: &[Joint], bodies: &mut [RigidBody], dt: Fix128) {
237 for motor in motors {
238 if motor.controller.mode == MotorMode::Off {
239 continue;
240 }
241
242 if motor.joint_index >= joints.len() {
243 continue;
244 }
245
246 let joint = &joints[motor.joint_index];
247 let (body_a_idx, body_b_idx) = joint.bodies();
248
249 if body_a_idx >= bodies.len() || body_b_idx >= bodies.len() {
250 continue;
251 }
252
253 let body_a = bodies[body_a_idx];
255 let body_b = bodies[body_b_idx];
256
257 if let Joint::Hinge(hinge) = joint {
258 let axis = body_a.rotation.rotate_vec(hinge.local_axis_a);
259 let rel_quat = body_b.rotation.mul(body_a.rotation.conjugate());
260 let current_pos = crate::joint::compute_twist_angle(rel_quat, axis);
261 let current_vel = (body_b.angular_velocity - body_a.angular_velocity).dot(axis);
262
263 let torque = motor.controller.compute(current_pos, current_vel);
264 if torque.is_zero() {
265 continue;
266 }
267
268 let angular_impulse = axis * (torque * dt);
269 apply_angular_pair(bodies, body_a_idx, body_b_idx, angular_impulse);
270 continue;
271 }
272
273 let delta = body_b.position - body_a.position;
274 let current_pos = delta.length();
275 let rel_vel = body_b.velocity - body_a.velocity;
276 let current_vel = if current_pos.is_zero() {
277 Fix128::ZERO
278 } else {
279 rel_vel.dot(delta / current_pos)
280 };
281
282 let force = motor.controller.compute(current_pos, current_vel);
283
284 if force.is_zero() || current_pos.is_zero() {
285 continue;
286 }
287
288 let direction = delta / current_pos;
289 let impulse = direction * (force * dt);
290
291 if !body_a.inv_mass.is_zero() {
292 bodies[body_a_idx].velocity = bodies[body_a_idx].velocity - impulse * body_a.inv_mass;
293 }
294 if !body_b.inv_mass.is_zero() {
295 bodies[body_b_idx].velocity = bodies[body_b_idx].velocity + impulse * body_b.inv_mass;
296 }
297 }
298}
299
300pub(crate) fn apply_motors_3d(
317 motors: &[(usize, PdController3D)],
318 joints: &[Joint],
319 bodies: &mut [RigidBody],
320 dt: Fix128,
321) {
322 for (joint_index, controller) in motors {
323 if controller.mode == MotorMode::Off || *joint_index >= joints.len() {
324 continue;
325 }
326 let (body_a_idx, body_b_idx) = joints[*joint_index].bodies();
327 if body_a_idx >= bodies.len() || body_b_idx >= bodies.len() {
328 continue;
329 }
330 let body_a = bodies[body_a_idx];
331 let body_b = bodies[body_b_idx];
332 let rel_quat = body_b.rotation.mul(body_a.rotation.conjugate());
333 let rel_omega = body_b.angular_velocity - body_a.angular_velocity;
334 let torque = controller.compute_torque(rel_quat, rel_omega);
335 if torque == Vec3Fix::ZERO {
336 continue;
337 }
338 apply_angular_pair(bodies, body_a_idx, body_b_idx, torque * dt);
339 }
340}
341
342fn apply_angular_pair(
345 bodies: &mut [RigidBody],
346 body_a_idx: usize,
347 body_b_idx: usize,
348 angular_impulse: Vec3Fix,
349) {
350 let body_a = bodies[body_a_idx];
351 let body_b = bodies[body_b_idx];
352 if !body_a.inv_mass.is_zero() {
353 bodies[body_a_idx].angular_velocity =
354 body_a.angular_velocity - body_a.world_inv_inertia_apply(angular_impulse);
355 }
356 if !body_b.inv_mass.is_zero() {
357 bodies[body_b_idx].angular_velocity =
358 bodies[body_b_idx].angular_velocity + body_b.world_inv_inertia_apply(angular_impulse);
359 }
360}
361
362#[inline]
369#[must_use]
370pub(crate) fn shortest_arc(q: QuatFix) -> QuatFix {
371 if q.w < Fix128::ZERO {
372 QuatFix::new(-q.x, -q.y, -q.z, -q.w)
373 } else {
374 q
375 }
376}
377
378#[inline]
379fn clamp(v: Fix128, min: Fix128, max: Fix128) -> Fix128 {
380 if v < min {
381 min
382 } else if v > max {
383 max
384 } else {
385 v
386 }
387}
388
389#[cfg(test)]
390mod tests {
391 use super::*;
392 use crate::joint::BallJoint;
393 #[cfg(not(feature = "std"))]
394 use alloc::{vec, vec::Vec};
395
396 #[test]
397 fn test_pd_position_control() {
398 let mut pd = PdController::new(
399 Fix128::from_int(10),
400 Fix128::from_int(1),
401 Fix128::from_int(100),
402 );
403 pd.set_position_target(Fix128::from_int(5));
404
405 let output = pd.compute(Fix128::ZERO, Fix128::ZERO);
406 assert_eq!(output.hi, 50);
408 }
409
410 #[test]
411 fn test_pd_clamp() {
412 let mut pd = PdController::new(
413 Fix128::from_int(1000),
414 Fix128::ZERO,
415 Fix128::from_int(10), );
417 pd.set_position_target(Fix128::from_int(100));
418
419 let output = pd.compute(Fix128::ZERO, Fix128::ZERO);
420 assert_eq!(output.hi, 10, "Should be clamped to max_force");
421 }
422
423 #[test]
424 fn test_pd_off() {
425 let pd = PdController::new(
426 Fix128::from_int(10),
427 Fix128::from_int(1),
428 Fix128::from_int(100),
429 );
430 let output = pd.compute(Fix128::ZERO, Fix128::ZERO);
432 assert!(output.is_zero());
433 }
434
435 #[test]
436 fn test_velocity_mode() {
437 let mut pd = PdController::new(Fix128::from_int(10), Fix128::ZERO, Fix128::from_int(1000));
438 pd.set_velocity_target(Fix128::from_int(5));
439
440 let output = pd.compute(Fix128::ZERO, Fix128::ZERO);
441 assert_eq!(output.hi, 50);
443 }
444
445 #[test]
446 fn test_3d_controller() {
447 let mut pd = PdController3D::new(
448 Vec3Fix::from_int(100, 100, 100),
449 Vec3Fix::from_int(10, 10, 10),
450 Fix128::from_int(1000),
451 );
452 pd.set_rotation_target(QuatFix::IDENTITY);
453 pd.mode = MotorMode::Position;
454
455 let torque = pd.compute_torque(QuatFix::IDENTITY, Vec3Fix::ZERO);
456 let mag = torque.length();
458 assert!(
459 mag < Fix128::from_ratio(1, 10),
460 "Zero error should give zero torque"
461 );
462 }
463
464 fn motor_scene(ia: i64, ib: i64) -> (Vec<RigidBody>, Vec<Joint>) {
466 let mut a = RigidBody::new_dynamic(Vec3Fix::ZERO, Fix128::ONE);
467 let mut b = RigidBody::new_dynamic(Vec3Fix::from_int(4, 0, 0), Fix128::ONE);
468 a.inv_mass = Fix128::from_int(ia);
469 b.inv_mass = Fix128::from_int(ib);
470 let joints = vec![Joint::Ball(BallJoint::new(
471 0,
472 1,
473 Vec3Fix::ZERO,
474 Vec3Fix::ZERO,
475 ))];
476 (vec![a, b], joints)
477 }
478
479 #[test]
480 fn apply_motors_position_mode_pushes_along_joint_axis_by_force_dt_inv_mass() {
481 let (mut bodies, joints) = motor_scene(0, 1);
484 let mut pd = PdController::new(Fix128::from_int(10), Fix128::ZERO, Fix128::from_int(100));
485 pd.set_position_target(Fix128::from_int(6));
486 let motors = [JointMotor::new(0, pd)];
487 apply_motors(&motors, &joints, &mut bodies, Fix128::from_ratio(1, 4));
488 assert_eq!(bodies[1].velocity, Vec3Fix::from_int(5, 0, 0));
489 assert_eq!(bodies[0].velocity, Vec3Fix::ZERO);
490
491 let (mut closer, joints) = motor_scene(0, 1);
493 let mut pd_in = pd;
494 pd_in.set_position_target(Fix128::from_int(2));
495 apply_motors(
496 &[JointMotor::new(0, pd_in)],
497 &joints,
498 &mut closer,
499 Fix128::from_ratio(1, 4),
500 );
501 assert_eq!(closer[1].velocity, Vec3Fix::from_int(-5, 0, 0));
502
503 let (mut both, joints) = motor_scene(1, 2);
505 apply_motors(
506 &[JointMotor::new(0, pd)],
507 &joints,
508 &mut both,
509 Fix128::from_ratio(1, 4),
510 );
511 assert_eq!(both[0].velocity, Vec3Fix::from_int(-5, 0, 0));
512 assert_eq!(both[1].velocity, Vec3Fix::from_int(10, 0, 0));
513
514 let (mut clamped, joints) = motor_scene(0, 1);
516 let mut pd_clamp =
517 PdController::new(Fix128::from_int(10), Fix128::ZERO, Fix128::from_int(8));
518 pd_clamp.set_position_target(Fix128::from_int(6));
519 apply_motors(
520 &[JointMotor::new(0, pd_clamp)],
521 &joints,
522 &mut clamped,
523 Fix128::from_ratio(1, 4),
524 );
525 assert_eq!(clamped[1].velocity, Vec3Fix::from_int(2, 0, 0));
526 }
527
528 #[test]
529 fn apply_motors_velocity_mode_uses_relative_velocity_along_axis() {
530 let (mut bodies, joints) = motor_scene(0, 1);
533 bodies[1].velocity = Vec3Fix::from_int(1, 7, 0);
534 let mut pd = PdController::new(Fix128::from_int(10), Fix128::ZERO, Fix128::from_int(100));
535 pd.set_velocity_target(Fix128::from_int(3));
536 apply_motors(
537 &[JointMotor::new(0, pd)],
538 &joints,
539 &mut bodies,
540 Fix128::from_ratio(1, 4),
541 );
542 assert_eq!(bodies[1].velocity, Vec3Fix::from_int(6, 7, 0));
543 }
544
545 #[test]
546 fn apply_motors_skips_off_out_of_range_zero_force_and_coincident_bodies() {
547 let (mut bodies, joints) = motor_scene(0, 1);
548 let mut pd = PdController::new(Fix128::from_int(10), Fix128::ZERO, Fix128::from_int(100));
549 pd.set_position_target(Fix128::from_int(6));
550 let dt = Fix128::from_ratio(1, 4);
551
552 let mut off = pd;
554 off.disable();
555 apply_motors(&[JointMotor::new(0, off)], &joints, &mut bodies, dt);
556 assert_eq!(bodies[1].velocity, Vec3Fix::ZERO);
557 apply_motors(&[JointMotor::new(1, pd)], &joints, &mut bodies, dt);
559 assert_eq!(bodies[1].velocity, Vec3Fix::ZERO);
560 let far = [Joint::Ball(BallJoint::new(
562 0,
563 5,
564 Vec3Fix::ZERO,
565 Vec3Fix::ZERO,
566 ))];
567 apply_motors(&[JointMotor::new(0, pd)], &far, &mut bodies, dt);
568 assert_eq!(bodies[1].velocity, Vec3Fix::ZERO);
569 let mut at_target = pd;
571 at_target.set_position_target(Fix128::from_int(4));
572 apply_motors(&[JointMotor::new(0, at_target)], &joints, &mut bodies, dt);
573 assert_eq!(bodies[1].velocity, Vec3Fix::ZERO);
574 bodies[1].position = Vec3Fix::ZERO;
576 apply_motors(&[JointMotor::new(0, pd)], &joints, &mut bodies, dt);
577 assert_eq!(bodies[1].velocity, Vec3Fix::ZERO);
578 bodies[1].position = Vec3Fix::from_int(4, 0, 0);
580 apply_motors(&[JointMotor::new(0, pd)], &joints, &mut bodies, dt);
581 assert_eq!(bodies[1].velocity, Vec3Fix::from_int(5, 0, 0));
582 }
583
584 fn assert_vec_near(actual: Vec3Fix, expected: Vec3Fix, what: &str) {
585 let tol = Fix128::from_ratio(1, 1_000_000_000);
586 let d = actual - expected;
587 assert!(
588 d.x.abs() < tol && d.y.abs() < tol && d.z.abs() < tol,
589 "{what}: {actual:?} vs {expected:?}"
590 );
591 }
592
593 fn turn_z(sign: i64) -> QuatFix {
596 let s = Fix128::from_int(sign);
597 QuatFix::new(
598 Fix128::ZERO,
599 Fix128::ZERO,
600 Fix128::from_ratio(4, 5) * s,
601 Fix128::from_ratio(3, 5) * s,
602 )
603 }
604
605 #[test]
613 fn controller_3d_position_mode_follows_the_shortest_arc_and_clamps_the_magnitude() {
614 let mut pd = PdController3D::new(
615 Vec3Fix::from_int(10, 10, 10),
616 Vec3Fix::from_int(1, 1, 1),
617 Fix128::from_int(100),
618 );
619 pd.set_rotation_target(QuatFix::IDENTITY);
620 let omega = Vec3Fix::from_int(0, 0, 2);
621 let expected = Vec3Fix::from_int(0, 0, -18);
622 assert_vec_near(pd.compute_torque(turn_z(1), omega), expected, "w > 0");
623 assert_vec_near(pd.compute_torque(turn_z(-1), omega), expected, "w < 0");
624
625 pd.max_torque = Fix128::from_int(9);
626 assert_vec_near(
627 pd.compute_torque(turn_z(-1), omega),
628 Vec3Fix::from_int(0, 0, -9),
629 "clamped",
630 );
631
632 pd.mode = MotorMode::Off;
633 assert_eq!(pd.compute_torque(turn_z(1), omega), Vec3Fix::ZERO);
634 }
635
636 #[test]
641 fn controller_3d_velocity_mode_is_per_axis_proportional_and_clamped_by_magnitude() {
642 let mut pd = PdController3D::new(
643 Vec3Fix::from_int(2, 3, 4),
644 Vec3Fix::ZERO,
645 Fix128::from_int(100),
646 );
647 pd.target_angular_velocity = Vec3Fix::from_int(1, 1, 1);
648 pd.mode = MotorMode::Velocity;
649 assert_eq!(
650 pd.compute_torque(QuatFix::IDENTITY, Vec3Fix::from_int(0, 1, 0)),
651 Vec3Fix::from_int(2, 0, 4)
652 );
653
654 pd.kp = Vec3Fix::from_int(10, 10, 10);
655 pd.max_torque = Fix128::from_int(25);
656 pd.target_angular_velocity = Vec3Fix::from_int(3, 4, 0);
657 assert_vec_near(
658 pd.compute_torque(QuatFix::IDENTITY, Vec3Fix::ZERO),
659 Vec3Fix::from_int(15, 20, 0),
660 "clamped",
661 );
662 }
663
664 fn spinning_pair() -> (Vec<RigidBody>, Vec<Joint>) {
667 let (mut bodies, joints) = motor_scene(1, 1);
668 bodies[0].inv_inertia = Vec3Fix::from_int(1, 1, 1);
669 bodies[1].inv_inertia = Vec3Fix::from_int(2, 2, 2);
670 (bodies, joints)
671 }
672
673 #[test]
680 fn apply_motors_3d_applies_equal_and_opposite_angular_impulses() {
681 let mut pd = PdController3D::new(
682 Vec3Fix::from_int(2, 3, 4),
683 Vec3Fix::ZERO,
684 Fix128::from_int(100),
685 );
686 pd.target_angular_velocity = Vec3Fix::from_int(1, 1, 1);
687 pd.mode = MotorMode::Velocity;
688 let dt = Fix128::from_ratio(1, 2);
689
690 let (mut bodies, joints) = spinning_pair();
691 bodies[1].angular_velocity = Vec3Fix::from_int(0, 1, 0);
692 apply_motors_3d(&[(0, pd)], &joints, &mut bodies, dt);
693 assert_eq!(bodies[0].angular_velocity, Vec3Fix::from_int(-1, 0, -2));
694 assert_eq!(bodies[1].angular_velocity, Vec3Fix::from_int(2, 1, 4));
695 let half = Fix128::from_ratio(1, 2);
696 let momentum = bodies[0].angular_velocity + bodies[1].angular_velocity * half;
697 assert_eq!(momentum, Vec3Fix::new(Fix128::ZERO, half, Fix128::ZERO));
698
699 let (mut fixed_a, joints) = spinning_pair();
700 fixed_a[0].inv_mass = Fix128::ZERO;
701 fixed_a[1].angular_velocity = Vec3Fix::from_int(0, 1, 0);
702 apply_motors_3d(&[(0, pd)], &joints, &mut fixed_a, dt);
703 assert_eq!(fixed_a[0].angular_velocity, Vec3Fix::ZERO);
704 assert_eq!(fixed_a[1].angular_velocity, Vec3Fix::from_int(2, 1, 4));
705 }
706
707 #[test]
712 fn apply_motors_3d_skips_off_out_of_range_and_zero_torque() {
713 let mut pd = PdController3D::new(
714 Vec3Fix::from_int(1, 1, 1),
715 Vec3Fix::ZERO,
716 Fix128::from_int(100),
717 );
718 pd.target_angular_velocity = Vec3Fix::from_int(0, 0, 1);
719 pd.mode = MotorMode::Velocity;
720 let dt = Fix128::ONE;
721 let (mut bodies, joints) = spinning_pair();
722 let mut off = pd;
723 off.mode = MotorMode::Off;
724 apply_motors_3d(&[(0, off), (1, pd)], &joints, &mut bodies, dt);
725 let far = [Joint::Ball(BallJoint::new(
726 0,
727 5,
728 Vec3Fix::ZERO,
729 Vec3Fix::ZERO,
730 ))];
731 apply_motors_3d(&[(0, pd)], &far, &mut bodies, dt);
732 assert_eq!(bodies[0].angular_velocity, Vec3Fix::ZERO);
733 assert_eq!(bodies[1].angular_velocity, Vec3Fix::ZERO);
734
735 bodies[1].angular_velocity = Vec3Fix::from_int(0, 0, 1);
736 apply_motors_3d(&[(0, pd)], &joints, &mut bodies, dt);
737 assert_eq!(bodies[0].angular_velocity, Vec3Fix::ZERO);
738 assert_eq!(bodies[1].angular_velocity, Vec3Fix::from_int(0, 0, 1));
739
740 bodies[1].angular_velocity = Vec3Fix::ZERO;
741 apply_motors_3d(&[(0, pd)], &joints, &mut bodies, dt);
742 assert_eq!(bodies[0].angular_velocity, Vec3Fix::from_int(0, 0, -1));
743 assert_eq!(bodies[1].angular_velocity, Vec3Fix::from_int(0, 0, 2));
744 }
745
746 #[test]
753 fn hinge_motor_drives_the_relative_rate_about_the_axis_and_clamps_below() {
754 let hinge = || {
755 Joint::Hinge(crate::joint::HingeJoint::new(
756 0,
757 1,
758 Vec3Fix::ZERO,
759 Vec3Fix::ZERO,
760 Vec3Fix::UNIT_Y,
761 Vec3Fix::UNIT_Y,
762 ))
763 };
764 let pair = || {
765 let (mut bodies, _) = motor_scene(1, 1);
766 bodies[0].inv_inertia = Vec3Fix::from_int(1, 1, 1);
767 bodies[1].inv_inertia = Vec3Fix::from_int(1, 1, 1);
768 bodies
769 };
770 let dt = Fix128::from_ratio(1, 4);
771 let mut pd = PdController::new(Fix128::from_int(4), Fix128::ZERO, Fix128::from_int(100));
772 pd.set_velocity_target(Fix128::ONE);
773
774 let mut bodies = pair();
775 apply_motors(&[JointMotor::new(0, pd)], &[hinge()], &mut bodies, dt);
776 assert_eq!(bodies[0].angular_velocity, Vec3Fix::from_int(0, -1, 0));
777 assert_eq!(bodies[1].angular_velocity, Vec3Fix::from_int(0, 1, 0));
778
779 let mut slow = pd;
780 slow.max_force = Fix128::from_int(2);
781 slow.set_velocity_target(Fix128::from_int(-10));
782 let mut bodies = pair();
783 apply_motors(&[JointMotor::new(0, slow)], &[hinge()], &mut bodies, dt);
784 let half = Fix128::from_ratio(1, 2);
785 assert_eq!(
786 bodies[0].angular_velocity,
787 Vec3Fix::new(Fix128::ZERO, half, Fix128::ZERO)
788 );
789 assert_eq!(
790 bodies[1].angular_velocity,
791 Vec3Fix::new(Fix128::ZERO, -half, Fix128::ZERO)
792 );
793
794 let mut bodies = pair();
795 bodies[1].angular_velocity = Vec3Fix::from_int(0, 1, 0);
796 apply_motors(&[JointMotor::new(0, pd)], &[hinge()], &mut bodies, dt);
797 assert_eq!(bodies[0].angular_velocity, Vec3Fix::ZERO);
798 assert_eq!(bodies[1].angular_velocity, Vec3Fix::from_int(0, 1, 0));
799 }
800}