345 const Eigen::Vector2f goalPosition =
346 trajectory.points().back().waypoint.pose.translation().head<2>();
349 const Eigen::Vector2f goalDirection =
355 std::size_t settledSteps = 0;
357 Eigen::Vector2f position = start.translation().head<2>();
358 float yaw = yawOf(start);
362 Eigen::Vector2f filteredLinear = Eigen::Vector2f::Zero();
363 float filteredAngular = 0.F;
365 Eigen::Vector2f previousVelocityGlobal = Eigen::Vector2f::Zero();
366 float previousAngularVelocity = 0.F;
376 PlanarRateLimit rampLinear(
382 const auto rampSubSteps =
383 std::max<std::size_t>(1,
static_cast<std::size_t
>(std::lround(
384 params_.dt / std::max(rateLimit.
dt, 1e-6F))));
392 <<
" sub-steps of " << rateLimit.
dt <<
" s per control cycle, "
399 ARMARX_INFO <<
"Device command ramp disabled: the raw controller twist goes "
400 "straight to the platform.";
404 Eigen::Vector2f velocityGlobal = Eigen::Vector2f::Zero();
405 float angularVelocity = 0.F;
407 const float maxDeltaV = params_.dynamics.maxLinearAcceleration * params_.dt;
408 const float maxDeltaOmega = params_.dynamics.maxAngularAcceleration * params_.dt;
425 Eigen::MatrixXf jacobianMM;
426 Eigen::MatrixXf inverseJacobianMM;
430 jacobianMM = params_.dynamics.mecanum.kinematics.J();
431 inverseJacobianMM = params_.dynamics.mecanum.kinematics.J_inv();
435 jacobianMM = params_.dynamics.omniWheel.kinematics.C();
436 inverseJacobianMM = jacobianMM.inverse();
439 const Eigen::Index wheelCount = jacobianMM.cols();
441 const float maxWheelVelocity = usesMecanum
442 ? params_.dynamics.mecanum.maxWheelVelocity
443 : params_.dynamics.omniWheel.maxWheelVelocity;
444 Eigen::VectorXf wheelVelocities = Eigen::VectorXf::Zero(wheelCount);
451 std::optional<PlatformInertia> inertia;
452 Eigen::MatrixXd wheelJacobianT = Eigen::MatrixXd::Identity(3, 3);
453 Eigen::MatrixXd inverseWheelJacobianT = Eigen::MatrixXd::Identity(3, 3);
454 float maxWheelSpeedFromDrive = 0.F;
458 inertia.emplace(drive.
robot);
463 const Eigen::Matrix3d mmToM = Eigen::Vector3d(1e-3, 1e-3, 1.0).asDiagonal();
464 const double toRadians = usesMecanum ? 1.0 : (2.0 *
M_PI);
466 const Eigen::MatrixXd jacobianSI =
467 mmToM * jacobianMM.cast<
double>() / toRadians;
468 const Eigen::MatrixXd inverseJacobianSI =
469 inverseJacobianMM.cast<
double>() * mmToM.inverse() * toRadians;
474 wheelJacobianT = jacobianSI.transpose();
475 inverseWheelJacobianT = inverseJacobianSI.transpose();
479 const float inputSpeedRpm =
486 const double toNativeWheelUnit = usesMecanum ? (2.0 *
M_PI) : 1.0;
487 maxWheelSpeedFromDrive = inputSpeedRpm / 60.F / drive.
gearRatio *
488 static_cast<float>(toNativeWheelUnit);
490 ARMARX_IMPORTANT << (usesMecanum ?
"Mecanum" :
"Omni-wheel") <<
" torque model: "
491 << wheelCount <<
" wheels, gear " << drive.
gearRatio <<
":1, "
493 << maxWheelSpeedFromDrive << (usesMecanum ?
" rad/s (" :
" rev/s (")
494 << inputSpeedRpm <<
" rpm at the gearbox input).";
499 if (inertia.has_value())
501 result.
massMatrix = inertia->massMatrix(Eigen::Vector3d::Zero());
506 const auto steps =
static_cast<std::size_t
>(params_.maxDuration / params_.dt);
509 for (std::size_t step = 0; step < steps; step++)
511 const float time =
static_cast<float>(step) * params_.dt;
512 const core::Pose global_T_robot = poseFrom(position, yaw);
518 const float alpha = params_.controller.alpha;
520 alpha * filteredLinear + (1.F - alpha) * controlResult.
twist.
linear.head<2>();
522 alpha * filteredAngular + (1.F - alpha) * controlResult.
twist.
angular.z();
529 const Eigen::Vector2f targetLinear = filteredLinear;
530 const float targetAngular = filteredAngular;
532 for (std::size_t
sub = 0;
sub < rampSubSteps;
sub++)
536 filteredLinear = rampLinear.update(targetLinear, rateLimit.
dt);
540 filteredLinear.x() = rampX.update(targetLinear.x(), rateLimit.
dt);
541 filteredLinear.y() = rampY.update(targetLinear.y(), rateLimit.
dt);
544 filteredAngular = rampYaw.update(targetAngular, rateLimit.
dt);
550 const Eigen::Rotation2Df global_R_robot(yaw);
551 const Eigen::Vector2f commandedVelocityGlobal = global_R_robot * filteredLinear;
554 const Eigen::Vector2f deltaV = commandedVelocityGlobal - velocityGlobal;
555 const float deltaOmega = filteredAngular - angularVelocity;
575 constexpr double mmToM = 1e-3;
577 const Eigen::Vector3d
q{position.x() * mmToM, position.y() * mmToM, yaw};
578 const Eigen::Vector3d qdot{velocityGlobal.x() * mmToM,
579 velocityGlobal.y() * mmToM,
582 const Eigen::Vector3d desiredAcceleration{deltaV.x() * mmToM / params_.dt,
583 deltaV.y() * mmToM / params_.dt,
584 deltaOmega / params_.dt};
586 const Eigen::Matrix3d massMatrix = inertia->massMatrix(
q);
587 const Eigen::Vector3d biasForce = inertia->bias(
q, qdot);
590 const Eigen::Vector3d requiredWrenchWorld =
591 massMatrix * desiredAcceleration + biasForce;
593 const Eigen::Rotation2Dd worldFromRobot(yaw);
594 Eigen::Vector3d requiredWrenchBody;
595 requiredWrenchBody.head<2>() =
596 worldFromRobot.inverse() * requiredWrenchWorld.head<2>();
597 requiredWrenchBody.z() = requiredWrenchWorld.z();
599 const Eigen::VectorXd requiredWheelTorque = wheelJacobianT * requiredWrenchBody;
600 const Eigen::VectorXd requiredMotorTorque =
605 Eigen::VectorXd appliedMotorTorque = requiredMotorTorque;
606 for (Eigen::Index motor = 0; motor < wheelCount; motor++)
610 appliedMotorTorque[motor] =
616 const Eigen::Vector3d appliedWrenchBody =
617 inverseWheelJacobianT *
620 Eigen::Vector3d appliedWrenchWorld;
621 appliedWrenchWorld.head<2>() = worldFromRobot * appliedWrenchBody.head<2>();
622 appliedWrenchWorld.z() = appliedWrenchBody.z();
624 const Eigen::Vector3d appliedAcceleration =
625 massMatrix.ldlt().solve(appliedWrenchWorld - biasForce);
627 velocityGlobal.x() +=
628 static_cast<float>(appliedAcceleration.x() / mmToM * params_.dt);
629 velocityGlobal.y() +=
630 static_cast<float>(appliedAcceleration.y() / mmToM * params_.dt);
631 angularVelocity +=
static_cast<float>(appliedAcceleration.z() * params_.dt);
635 Eigen::Vector3f bodyTwist;
636 bodyTwist.head<2>() = Eigen::Rotation2Df(yaw).inverse() * velocityGlobal;
637 bodyTwist.z() = angularVelocity;
639 Eigen::VectorXf wheels = inverseJacobianMM * bodyTwist;
640 bool speedClamped =
false;
641 for (Eigen::Index wheel = 0; wheel < wheelCount; wheel++)
643 if (std::abs(wheels[wheel]) > maxWheelSpeedFromDrive)
646 std::copysign(maxWheelSpeedFromDrive, wheels[wheel]);
654 const Eigen::Vector3f clampedTwist =
656 velocityGlobal = Eigen::Rotation2Df(yaw) * clampedTwist.head<2>();
657 angularVelocity = clampedTwist.z();
660 wheelVelocities = wheels;
665 inverseJacobianMM * (Eigen::Vector3f{
666 filteredLinear.x(), filteredLinear.y(), filteredAngular});
673 static_cast<float>(requiredMotorTorque.cwiseAbs().maxCoeff()));
681 const Eigen::Vector3f commandedTwistLocal{
682 filteredLinear.x(), filteredLinear.y(), filteredAngular};
683 const Eigen::VectorXf commandedWheels =
684 inverseJacobianMM * commandedTwistLocal;
686 Eigen::VectorXf appliedDeltaW = commandedWheels - wheelVelocities;
689 for (Eigen::Index wheel = 0; wheel < wheelCount; wheel++)
693 const bool decelerating = std::abs(commandedWheels[wheel]) <
694 std::abs(wheelVelocities[wheel]);
696 (decelerating ? (usesMecanum
697 ? params_.dynamics.mecanum.maxWheelDeceleration
698 : params_.dynamics.omniWheel.maxWheelDeceleration)
700 ? params_.dynamics.mecanum.maxWheelAcceleration
701 : params_.dynamics.omniWheel.maxWheelAcceleration)) *
704 if (std::abs(appliedDeltaW[wheel]) > bound)
706 appliedDeltaW[wheel] = std::copysign(bound, appliedDeltaW[wheel]);
711 wheelVelocities += appliedDeltaW;
712 wheelVelocities = wheelVelocities.cwiseMax(-maxWheelVelocity)
713 .cwiseMin(maxWheelVelocity);
715 const Eigen::Vector3f achievedTwistLocal =
716 jacobianMM * wheelVelocities;
718 velocityGlobal = global_R_robot * achievedTwistLocal.head<2>();
719 angularVelocity = achievedTwistLocal.z();
727 const bool linearSaturated = deltaV.norm() > maxDeltaV;
728 const bool angularSaturated = std::abs(deltaOmega) > maxDeltaOmega;
731 const Eigen::Vector2f appliedDeltaV =
732 linearSaturated ? (deltaV.normalized() * maxDeltaV).eval() : deltaV;
733 const float appliedDeltaOmega =
734 std::clamp(deltaOmega, -maxDeltaOmega, maxDeltaOmega);
736 velocityGlobal += appliedDeltaV;
737 angularVelocity += appliedDeltaOmega;
748 sample.
speed = velocityGlobal.norm();
751 (velocityGlobal - previousVelocityGlobal).
norm() / params_.dt;
753 std::abs(angularVelocity - previousAngularVelocity) / params_.dt;
767 result.
samples.push_back(sample);
769 previousVelocityGlobal = velocityGlobal;
770 previousAngularVelocity = angularVelocity;
773 position += velocityGlobal * params_.dt;
774 yaw += angularVelocity * params_.dt;
776 const float distanceToGoal = (position - goalPosition).
norm();
781 if ((position - goalPosition).
dot(goalDirection) > 0.F)
786 if (distanceToGoal < params_.goalDistanceThreshold)
794 settledSteps = (velocityGlobal.norm() < params_.settledSpeedThreshold) ? settledSteps + 1 : 0;
796 if (
static_cast<float>(settledSteps) * params_.dt > params_.settledDuration)
807 << params_.goalDistanceThreshold <<
" mm threshold.";
811 ARMARX_WARNING <<
"Simulation did not reach the goal within " << params_.maxDuration
823 << (
static_cast<float>(result.
samples.size()) * params_.dt)
824 <<
" s). Peak required acceleration: "
826 << params_.dynamics.maxLinearAcceleration <<
"), "
828 << params_.dynamics.maxAngularAcceleration <<
"). Saturated in "