344 const Eigen::Vector2f goalPosition =
345 trajectory.points().back().waypoint.pose.translation().head<2>();
348 const Eigen::Vector2f goalDirection =
354 std::size_t settledSteps = 0;
356 Eigen::Vector2f position = start.translation().head<2>();
357 float yaw = yawOf(start);
361 Eigen::Vector2f filteredLinear = Eigen::Vector2f::Zero();
362 float filteredAngular = 0.F;
364 Eigen::Vector2f previousVelocityGlobal = Eigen::Vector2f::Zero();
365 float previousAngularVelocity = 0.F;
375 PlanarRateLimit rampLinear(
381 const auto rampSubSteps =
382 std::max<std::size_t>(1,
static_cast<std::size_t
>(std::lround(
383 params_.dt / std::max(rateLimit.
dt, 1e-6F))));
391 <<
" sub-steps of " << rateLimit.
dt <<
" s per control cycle, "
398 ARMARX_INFO <<
"Device command ramp disabled: the raw controller twist goes "
399 "straight to the platform.";
403 Eigen::Vector2f velocityGlobal = Eigen::Vector2f::Zero();
404 float angularVelocity = 0.F;
406 const float maxDeltaV = params_.dynamics.maxLinearAcceleration * params_.dt;
407 const float maxDeltaOmega = params_.dynamics.maxAngularAcceleration * params_.dt;
424 Eigen::MatrixXf jacobianMM;
425 Eigen::MatrixXf inverseJacobianMM;
429 jacobianMM = params_.dynamics.mecanum.kinematics.J();
430 inverseJacobianMM = params_.dynamics.mecanum.kinematics.J_inv();
434 jacobianMM = params_.dynamics.omniWheel.kinematics.C();
435 inverseJacobianMM = jacobianMM.inverse();
438 const Eigen::Index wheelCount = jacobianMM.cols();
440 const float maxWheelVelocity = usesMecanum
441 ? params_.dynamics.mecanum.maxWheelVelocity
442 : params_.dynamics.omniWheel.maxWheelVelocity;
443 Eigen::VectorXf wheelVelocities = Eigen::VectorXf::Zero(wheelCount);
450 std::optional<PlatformInertia> inertia;
451 Eigen::MatrixXd wheelJacobianT = Eigen::MatrixXd::Identity(3, 3);
452 Eigen::MatrixXd inverseWheelJacobianT = Eigen::MatrixXd::Identity(3, 3);
453 float maxWheelSpeedFromDrive = 0.F;
457 inertia.emplace(drive.
robot);
462 const Eigen::Matrix3d mmToM = Eigen::Vector3d(1e-3, 1e-3, 1.0).asDiagonal();
463 const double toRadians = usesMecanum ? 1.0 : (2.0 *
M_PI);
465 const Eigen::MatrixXd jacobianSI =
466 mmToM * jacobianMM.cast<
double>() / toRadians;
467 const Eigen::MatrixXd inverseJacobianSI =
468 inverseJacobianMM.cast<
double>() * mmToM.inverse() * toRadians;
473 wheelJacobianT = jacobianSI.transpose();
474 inverseWheelJacobianT = inverseJacobianSI.transpose();
478 const float inputSpeedRpm =
485 const double toNativeWheelUnit = usesMecanum ? (2.0 *
M_PI) : 1.0;
486 maxWheelSpeedFromDrive = inputSpeedRpm / 60.F / drive.
gearRatio *
487 static_cast<float>(toNativeWheelUnit);
489 ARMARX_IMPORTANT << (usesMecanum ?
"Mecanum" :
"Omni-wheel") <<
" torque model: "
490 << wheelCount <<
" wheels, gear " << drive.
gearRatio <<
":1, "
492 << maxWheelSpeedFromDrive << (usesMecanum ?
" rad/s (" :
" rev/s (")
493 << inputSpeedRpm <<
" rpm at the gearbox input).";
498 if (inertia.has_value())
500 result.
massMatrix = inertia->massMatrix(Eigen::Vector3d::Zero());
505 const auto steps =
static_cast<std::size_t
>(params_.maxDuration / params_.dt);
508 for (std::size_t step = 0; step < steps; step++)
510 const float time =
static_cast<float>(step) * params_.dt;
511 const core::Pose global_T_robot = poseFrom(position, yaw);
517 const float alpha = params_.controller.alpha;
519 alpha * filteredLinear + (1.F - alpha) * controlResult.
twist.
linear.head<2>();
521 alpha * filteredAngular + (1.F - alpha) * controlResult.
twist.
angular.z();
528 const Eigen::Vector2f targetLinear = filteredLinear;
529 const float targetAngular = filteredAngular;
531 for (std::size_t
sub = 0;
sub < rampSubSteps;
sub++)
535 filteredLinear = rampLinear.update(targetLinear, rateLimit.
dt);
539 filteredLinear.x() = rampX.update(targetLinear.x(), rateLimit.
dt);
540 filteredLinear.y() = rampY.update(targetLinear.y(), rateLimit.
dt);
543 filteredAngular = rampYaw.update(targetAngular, rateLimit.
dt);
549 const Eigen::Rotation2Df global_R_robot(yaw);
550 const Eigen::Vector2f commandedVelocityGlobal = global_R_robot * filteredLinear;
553 const Eigen::Vector2f deltaV = commandedVelocityGlobal - velocityGlobal;
554 const float deltaOmega = filteredAngular - angularVelocity;
574 constexpr double mmToM = 1e-3;
576 const Eigen::Vector3d
q{position.x() * mmToM, position.y() * mmToM, yaw};
577 const Eigen::Vector3d qdot{velocityGlobal.x() * mmToM,
578 velocityGlobal.y() * mmToM,
581 const Eigen::Vector3d desiredAcceleration{deltaV.x() * mmToM / params_.dt,
582 deltaV.y() * mmToM / params_.dt,
583 deltaOmega / params_.dt};
585 const Eigen::Matrix3d massMatrix = inertia->massMatrix(
q);
586 const Eigen::Vector3d biasForce = inertia->bias(
q, qdot);
589 const Eigen::Vector3d requiredWrenchWorld =
590 massMatrix * desiredAcceleration + biasForce;
592 const Eigen::Rotation2Dd worldFromRobot(yaw);
593 Eigen::Vector3d requiredWrenchBody;
594 requiredWrenchBody.head<2>() =
595 worldFromRobot.inverse() * requiredWrenchWorld.head<2>();
596 requiredWrenchBody.z() = requiredWrenchWorld.z();
598 const Eigen::VectorXd requiredWheelTorque = wheelJacobianT * requiredWrenchBody;
599 const Eigen::VectorXd requiredMotorTorque =
604 Eigen::VectorXd appliedMotorTorque = requiredMotorTorque;
605 for (Eigen::Index motor = 0; motor < wheelCount; motor++)
609 appliedMotorTorque[motor] =
615 const Eigen::Vector3d appliedWrenchBody =
616 inverseWheelJacobianT *
619 Eigen::Vector3d appliedWrenchWorld;
620 appliedWrenchWorld.head<2>() = worldFromRobot * appliedWrenchBody.head<2>();
621 appliedWrenchWorld.z() = appliedWrenchBody.z();
623 const Eigen::Vector3d appliedAcceleration =
624 massMatrix.ldlt().solve(appliedWrenchWorld - biasForce);
626 velocityGlobal.x() +=
627 static_cast<float>(appliedAcceleration.x() / mmToM * params_.dt);
628 velocityGlobal.y() +=
629 static_cast<float>(appliedAcceleration.y() / mmToM * params_.dt);
630 angularVelocity +=
static_cast<float>(appliedAcceleration.z() * params_.dt);
634 Eigen::Vector3f bodyTwist;
635 bodyTwist.head<2>() = Eigen::Rotation2Df(yaw).inverse() * velocityGlobal;
636 bodyTwist.z() = angularVelocity;
638 Eigen::VectorXf wheels = inverseJacobianMM * bodyTwist;
639 bool speedClamped =
false;
640 for (Eigen::Index wheel = 0; wheel < wheelCount; wheel++)
642 if (std::abs(wheels[wheel]) > maxWheelSpeedFromDrive)
645 std::copysign(maxWheelSpeedFromDrive, wheels[wheel]);
653 const Eigen::Vector3f clampedTwist =
655 velocityGlobal = Eigen::Rotation2Df(yaw) * clampedTwist.head<2>();
656 angularVelocity = clampedTwist.z();
659 wheelVelocities = wheels;
664 inverseJacobianMM * (Eigen::Vector3f{
665 filteredLinear.x(), filteredLinear.y(), filteredAngular});
672 static_cast<float>(requiredMotorTorque.cwiseAbs().maxCoeff()));
680 const Eigen::Vector3f commandedTwistLocal{
681 filteredLinear.x(), filteredLinear.y(), filteredAngular};
682 const Eigen::VectorXf commandedWheels =
683 inverseJacobianMM * commandedTwistLocal;
685 Eigen::VectorXf appliedDeltaW = commandedWheels - wheelVelocities;
688 for (Eigen::Index wheel = 0; wheel < wheelCount; wheel++)
692 const bool decelerating = std::abs(commandedWheels[wheel]) <
693 std::abs(wheelVelocities[wheel]);
695 (decelerating ? (usesMecanum
696 ? params_.dynamics.mecanum.maxWheelDeceleration
697 : params_.dynamics.omniWheel.maxWheelDeceleration)
699 ? params_.dynamics.mecanum.maxWheelAcceleration
700 : params_.dynamics.omniWheel.maxWheelAcceleration)) *
703 if (std::abs(appliedDeltaW[wheel]) > bound)
705 appliedDeltaW[wheel] = std::copysign(bound, appliedDeltaW[wheel]);
710 wheelVelocities += appliedDeltaW;
711 wheelVelocities = wheelVelocities.cwiseMax(-maxWheelVelocity)
712 .cwiseMin(maxWheelVelocity);
714 const Eigen::Vector3f achievedTwistLocal =
715 jacobianMM * wheelVelocities;
717 velocityGlobal = global_R_robot * achievedTwistLocal.head<2>();
718 angularVelocity = achievedTwistLocal.z();
726 const bool linearSaturated = deltaV.norm() > maxDeltaV;
727 const bool angularSaturated = std::abs(deltaOmega) > maxDeltaOmega;
730 const Eigen::Vector2f appliedDeltaV =
731 linearSaturated ? (deltaV.normalized() * maxDeltaV).eval() : deltaV;
732 const float appliedDeltaOmega =
733 std::clamp(deltaOmega, -maxDeltaOmega, maxDeltaOmega);
735 velocityGlobal += appliedDeltaV;
736 angularVelocity += appliedDeltaOmega;
747 sample.
speed = velocityGlobal.norm();
750 (velocityGlobal - previousVelocityGlobal).
norm() / params_.dt;
752 std::abs(angularVelocity - previousAngularVelocity) / params_.dt;
766 result.
samples.push_back(sample);
768 previousVelocityGlobal = velocityGlobal;
769 previousAngularVelocity = angularVelocity;
772 position += velocityGlobal * params_.dt;
773 yaw += angularVelocity * params_.dt;
775 const float distanceToGoal = (position - goalPosition).
norm();
780 if ((position - goalPosition).
dot(goalDirection) > 0.F)
785 if (distanceToGoal < params_.goalDistanceThreshold)
793 settledSteps = (velocityGlobal.norm() < params_.settledSpeedThreshold) ? settledSteps + 1 : 0;
795 if (
static_cast<float>(settledSteps) * params_.dt > params_.settledDuration)
806 << params_.goalDistanceThreshold <<
" mm threshold.";
810 ARMARX_WARNING <<
"Simulation did not reach the goal within " << params_.maxDuration
822 << (
static_cast<float>(result.
samples.size()) * params_.dt)
823 <<
" s). Peak required acceleration: "
825 << params_.dynamics.maxLinearAcceleration <<
"), "
827 << params_.dynamics.maxAngularAcceleration <<
"). Saturated in "