diff --git a/VirtualRobot/CMakeLists.txt b/VirtualRobot/CMakeLists.txt index f09afe8aab09f035f22154b49bfd182bd360abe8..294ae06f6bca216b531b667718d03ff3f66a0895 100644 --- a/VirtualRobot/CMakeLists.txt +++ b/VirtualRobot/CMakeLists.txt @@ -333,8 +333,9 @@ SET(SOURCES Nodes/RobotNodeRevolute.cpp Nodes/RobotNodeRevoluteFactory.cpp Nodes/Sensor.cpp + Nodes/HemisphereJoint/CachedMaths.cpp Nodes/HemisphereJoint/Expressions.cpp - Nodes/HemisphereJoint/Joint.cpp + Nodes/HemisphereJoint/Maths.cpp TimeOptimalTrajectory/Path.cpp TimeOptimalTrajectory/TimeOptimalTrajectory.cpp @@ -577,8 +578,9 @@ SET(INCLUDES Nodes/RobotNodeRevoluteFactory.h Nodes/Sensor.h Nodes/SensorFactory.h + Nodes/HemisphereJoint/CachedMaths.h Nodes/HemisphereJoint/Expressions.h - Nodes/HemisphereJoint/Joint.h + Nodes/HemisphereJoint/Maths.h TimeOptimalTrajectory/Path.h TimeOptimalTrajectory/TimeOptimalTrajectory.h diff --git a/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.cpp b/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.cpp index f35569f728c3412be25276ca6d6fce59da010786..70a41bfbc7707cf74781d2587e57348e0ac187a1 100644 --- a/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.cpp +++ b/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.cpp @@ -206,35 +206,40 @@ void CompositeDiffIK::step(CompositeDiffIK::Parameters& params, SolveState& s, i ik->updatePseudoInverseJacobianMatrix(s.invJac, s.jacobi, 0, s.cartesianRegularization); + Eigen::VectorXf jv = s.invJac * cartesianVel; - Eigen::VectorXf nullspaceVel = Eigen::VectorXf::Zero(s.cols); - - for (const NullspaceGradientPtr& nsGradient : nullspaceGradients) + if (s.cols > s.rows) { - Eigen::VectorXf nsgrad = nsGradient->kP * nsGradient->getGradientAdjusted(params, stepNr); - nullspaceVel += nsgrad; - } - //LimitInfNormTo(nullspaceVel, params.maxJointAngleStep); + Eigen::VectorXf nullspaceVel = Eigen::VectorXf::Zero(s.cols); - Eigen::JacobiSVD<Eigen::MatrixXf> svd(s.jacobi, Eigen::ComputeFullU | Eigen::ComputeFullV); - Eigen::MatrixXf V = svd.matrixV(); - Eigen::MatrixXf nullspaceSVD = V.block(0, s.rows, s.cols, s.cols - s.rows); + for (const NullspaceGradientPtr& nsGradient : nullspaceGradients) + { + Eigen::VectorXf nsgrad = nsGradient->kP * nsGradient->getGradientAdjusted(params, stepNr); + nullspaceVel += nsgrad; + } - s.nullspace = nullspaceSVD; // CalculateNullspaceSVD(s.jacobi); + //LimitInfNormTo(nullspaceVel, params.maxJointAngleStep); - Eigen::VectorXf nsv = Eigen::VectorXf::Zero(s.cols); - for (int i = 0; i < s.nullspace.cols(); i++) - { - float squaredNorm = s.nullspace.col(i).squaredNorm(); - // Prevent division by zero - if (squaredNorm > 1.0e-32f) + Eigen::JacobiSVD<Eigen::MatrixXf> svd(s.jacobi, Eigen::ComputeFullU | Eigen::ComputeFullV); + Eigen::MatrixXf V = svd.matrixV(); + + Eigen::MatrixXf nullspaceSVD = V.block(0, s.rows, s.cols, s.cols - s.rows); + + s.nullspace = nullspaceSVD; // CalculateNullspaceSVD(s.jacobi); + + Eigen::VectorXf nsv = Eigen::VectorXf::Zero(s.cols); + for (int i = 0; i < s.nullspace.cols(); i++) { - nsv += s.nullspace.col(i) * s.nullspace.col(i).dot(nullspaceVel) / s.nullspace.col(i).squaredNorm(); + float squaredNorm = s.nullspace.col(i).squaredNorm(); + // Prevent division by zero + if (squaredNorm > 1.0e-32f) + { + nsv += s.nullspace.col(i) * s.nullspace.col(i).dot(nullspaceVel) / s.nullspace.col(i).squaredNorm(); + } } + jv = jv + nsv; } - Eigen::VectorXf jv = s.invJac * cartesianVel; - jv = jv + nsv; jv = jv * params.stepSize; jv = LimitInfNormTo(jv, params.maxJointAngleStep, params.maxJointAngleStepIgnore); jv = jv.cwiseProduct(s.jointRegularization); diff --git a/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.h b/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.h index 71e27d22469016e0a6a246255140428d4687f6a8..595b3b51f2e8ca0e408762f7473cdb15881c35e3 100644 --- a/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.h +++ b/VirtualRobot/IK/CompositeDiffIK/CompositeDiffIK.h @@ -38,7 +38,7 @@ namespace VirtualRobot // IK params size_t steps = 40; - float maxJointAngleStep = 0.2f; + float maxJointAngleStep = 0.01f; float stepSize = 0.5f; bool resetRnsValues = true; bool returnIKSteps = false; diff --git a/VirtualRobot/IK/DifferentialIK.cpp b/VirtualRobot/IK/DifferentialIK.cpp index 39d4ee31015b87431499b640a6b6aa1cebcb1b3d..1c64868f1abd9d3e90302fff7eda7610c12533a1 100644 --- a/VirtualRobot/IK/DifferentialIK.cpp +++ b/VirtualRobot/IK/DifferentialIK.cpp @@ -6,6 +6,7 @@ #include "../Nodes/RobotNodeRevolute.h" #include "../VirtualRobotException.h" #include "../CollisionDetection/CollisionChecker.h" +#include <VirtualRobot/Nodes/RobotNodeHemisphere.h> #include <Eigen/Geometry> @@ -380,9 +381,9 @@ namespace VirtualRobot //check if the tcp is affected by this DOF auto p = parents[tcpRN]; - if (find(p.begin(), p.end(), dof) != p.end()) + const bool isParent = std::find(p.begin(), p.end(), dof) != p.end(); + if (isParent) { - // Calculus for rotational joints is different as for prismatic joints. if (dof->isRotationalJoint()) { @@ -443,6 +444,29 @@ namespace VirtualRobot // no orientation part required with prismatic joints } + else if (dof->isHemisphereJoint()) + { + RobotNodeHemispherePtr hemisphere + = std::dynamic_pointer_cast<RobotNodeHemisphere>(dof); + + VR_ASSERT(hemisphere->first.has_value() xor hemisphere->second.has_value()); + + if (hemisphere->isSecondHemisphereJointNode()) + { + // Set Jacobian for both DoFs. + RobotNodeHemisphere::SecondData& second = hemisphere->getSecondData(); + const hemisphere::Maths::Jacobian jacobian = second.getJacobian(); + + tmpUpdateJacobianPosition.block<3, 2>(0, i - 1) + = jacobian.block<3, 2>(0, 0).cast<float>(); + tmpUpdateJacobianOrientation.block<3, 2>(0, i - 1) + = jacobian.block<3, 2>(3, 0).cast<float>(); + } + else + { + // Nothing to do - everything is handled by second DoF. + } + } } #ifdef CHECK_PERFORMANCE diff --git a/VirtualRobot/Nodes/HemisphereJoint/CachedMaths.cpp b/VirtualRobot/Nodes/HemisphereJoint/CachedMaths.cpp new file mode 100644 index 0000000000000000000000000000000000000000..e52ac985bb26927b5dfff6ee22e9d45a67a3f7b2 --- /dev/null +++ b/VirtualRobot/Nodes/HemisphereJoint/CachedMaths.cpp @@ -0,0 +1,15 @@ +#include "CachedMaths.h" + + +namespace VirtualRobot::hemisphere +{ + + void CachedMaths::update(const Eigen::Vector2f& actuators) + { + if (actuators != this->actuators) + { + maths.computeFkOfAngle(actuators.cast<double>()); + } + } + +} diff --git a/VirtualRobot/Nodes/HemisphereJoint/CachedMaths.h b/VirtualRobot/Nodes/HemisphereJoint/CachedMaths.h new file mode 100644 index 0000000000000000000000000000000000000000..9f2c139326e794a6763b99db25ccb1612e7eb0ee --- /dev/null +++ b/VirtualRobot/Nodes/HemisphereJoint/CachedMaths.h @@ -0,0 +1,34 @@ +#pragma once + +#include "Maths.h" + +#include <Eigen/Core> + + +namespace VirtualRobot::hemisphere +{ + + /** + * @brief Wrapper around `hemisphere::Joint` caching the results. + */ + class CachedMaths + { + public: + + /** + * @brief Recompute the maths if the given `actuators` differ from + * the stored `actuators`. + */ + void update(const Eigen::Vector2f& actuators); + + public: + + /// The actuator values that were used to compute the joint math. + Eigen::Vector2f actuators = Eigen::Vector2f::Constant(std::numeric_limits<float>::min()); + + /// The joint math. + hemisphere::Maths maths; + + }; + +} diff --git a/VirtualRobot/Nodes/HemisphereJoint/Joint.cpp b/VirtualRobot/Nodes/HemisphereJoint/Joint.cpp deleted file mode 100644 index 1d5a26ad82b6099338568961f1008b3cd415cf9d..0000000000000000000000000000000000000000 --- a/VirtualRobot/Nodes/HemisphereJoint/Joint.cpp +++ /dev/null @@ -1,97 +0,0 @@ -#include "Joint.h" - -#include <cmath> - -#include <SimoxUtility/math/convert/deg_to_rad.h> -#include <SimoxUtility/math/pose/pose.h> - - -namespace VirtualRobot::hemisphere -{ - - Joint::Joint() : - Joint(1, simox::math::deg_to_rad(25.)) - { - } - - - Joint::Joint(double lever, double theta0) - { - this->setConstants(lever, theta0); - } - - - void Joint::setConstants(double lever, double theta0) - { - this->lever = lever; - this->theta0 = theta0; - this->radius = 2 * std::sin(theta0) * lever; - - this->limitHi = simox::math::deg_to_rad(45 - 6.0); - this->limitLo = - simox::math::deg_to_rad(45 - 14.0); - } - - - void Joint::computeFkOfPosition(double p1, double p2) - { - fk.compute(p1, p2, lever, theta0); - } - - - void Joint::computeFkOfPosition(const Eigen::Vector2d& p12) - { - computeFkOfPosition(p12(0), p12(1)); - } - - - void Joint::computeFkOfAngle(const Eigen::Vector2d& alpha12) - { - computeFkOfPosition(angleToPosition(alpha12)); - } - - - Eigen::Vector3d Joint::getEndEffectorTranslation() const - { - return Eigen::Vector3d { - fk.ex, - fk.ey, - fk.ez - }; - } - - - Eigen::Matrix3d Joint::getEndEffectorRotation() const - { - // r_wrist_to_base = np.array([[exx, eyx, ezx], [exy, eyy, ezy], [exz, eyz, ezz]]) - Eigen::Matrix3d ori; - ori << fk.exx, fk.eyx, fk.ezx, - fk.exy, fk.eyy, fk.ezy, - fk.exz, fk.eyz, fk.ezz; - return ori; - } - - - Eigen::Matrix4d Joint::getEndEffectorTransform() const - { - return simox::math::pose(getEndEffectorTranslation(), getEndEffectorRotation()); - } - - - Joint::Jacobian Joint::getJacobian() const - { - Joint::Jacobian jacobian; - jacobian << fk.jx1, fk.jx2, - fk.jy1, fk.jy2, - fk.jz1, fk.jz2, - fk.jrx1, fk.jrx2, - fk.jry1, fk.jry2, - fk.jrz1, fk.jrz2; - return jacobian; - } - - Eigen::Vector2d Joint::angleToPosition(const Eigen::Vector2d& alpha) const - { - return lever * Eigen::sin((alpha + Eigen::Vector2d::Constant(theta0)).array()); - } - -} diff --git a/VirtualRobot/Nodes/HemisphereJoint/Joint.h b/VirtualRobot/Nodes/HemisphereJoint/Joint.h deleted file mode 100644 index 7d2bda4861ccf44c13f02d513a3f38378d6eafd1..0000000000000000000000000000000000000000 --- a/VirtualRobot/Nodes/HemisphereJoint/Joint.h +++ /dev/null @@ -1,54 +0,0 @@ -#pragma once - -#include <Eigen/Core> - -#include "Expressions.h" - - -namespace VirtualRobot::hemisphere -{ - - class Joint - { - public: - - using Jacobian = Eigen::Matrix<double, 6, 2>; - - public: - - Joint(); - Joint(double lever, double theta0); - - - void setConstants(double lever, double theta0); - - - void computeFkOfAngle(const Eigen::Vector2d& alpha12); - - void computeFkOfPosition(const Eigen::Vector2d& p12); - void computeFkOfPosition(double p1, double p2); - - - Eigen::Vector3d getEndEffectorTranslation() const; - Eigen::Matrix3d getEndEffectorRotation() const; - Eigen::Matrix4d getEndEffectorTransform() const; - Jacobian getJacobian() const; - - Eigen::Vector2d angleToPosition(const Eigen::Vector2d& alpha) const; - - - public: - - double lever = 0; - double theta0 = 0; - double radius = 0; - - double limitLo = 0; - double limitHi = 0; - - - Expressions fk; - - }; - -} diff --git a/VirtualRobot/Nodes/HemisphereJoint/Maths.cpp b/VirtualRobot/Nodes/HemisphereJoint/Maths.cpp new file mode 100644 index 0000000000000000000000000000000000000000..8ace6dfe80971894e197b8133b4e5cd5e12cecb2 --- /dev/null +++ b/VirtualRobot/Nodes/HemisphereJoint/Maths.cpp @@ -0,0 +1,122 @@ +#include "Maths.h" + +#include <cmath> + +#include <SimoxUtility/math/convert/deg_to_rad.h> +#include <SimoxUtility/math/pose/pose.h> + + +namespace VirtualRobot::hemisphere +{ + + Maths::Maths() : + Maths(1, simox::math::deg_to_rad(25.)) + { + } + + + Maths::Maths(double lever, double theta0) + { + this->setConstants(lever, theta0); + } + + + void Maths::setConstants(double lever, double theta0) + { + this->lever = lever; + this->theta0Rad = theta0; + this->radius = 2 * std::sin(theta0) * lever; + + this->limitHi = simox::math::deg_to_rad(45 - 6.0); + this->limitLo = - simox::math::deg_to_rad(45 - 14.0); + } + + + void Maths::computeFkOfPosition(double p1, double p2) + { + expressions.compute(p1, p2, lever, theta0Rad); + } + + + void Maths::computeFkOfPosition(const ActuatorPosition& p12) + { + computeFkOfPosition(p12(0), p12(1)); + } + + + void Maths::computeFkOfAngle(const ActuatorAngle& alpha12) + { + computeFkOfPosition(angleToPosition(alpha12)); + } + + + Eigen::Vector3d Maths::getEndEffectorTranslation() const + { + return Eigen::Vector3d { + expressions.ex, + expressions.ey, + expressions.ez + }; + } + + + Eigen::Matrix3d Maths::getEndEffectorRotation() const + { + // r_wrist_to_base = np.array([[exx, eyx, ezx], [exy, eyy, ezy], [exz, eyz, ezz]]) + Eigen::Matrix3d ori; + ori << expressions.exx, expressions.eyx, expressions.ezx, + expressions.exy, expressions.eyy, expressions.ezy, + expressions.exz, expressions.eyz, expressions.ezz; + return ori; + } + + + Eigen::Matrix4d Maths::getEndEffectorTransform() const + { + return simox::math::pose(getEndEffectorTranslation(), getEndEffectorRotation()); + } + + + Maths::Jacobian Maths::getJacobian() const + { + Maths::Jacobian jacobian; + jacobian << expressions.jx1, expressions.jx2, + expressions.jy1, expressions.jy2, + expressions.jz1, expressions.jz2, + expressions.jrx1, expressions.jrx2, + expressions.jry1, expressions.jry2, + expressions.jrz1, expressions.jrz2; + + // Current state of constructing the orientational part. + // ToDo: Do this with symbolic math inside `Expressions`. + { + const Eigen::Vector3d eefStateTrans = getEndEffectorTranslation(); + + for (int column = 0; column < 2; ++column) + { + // Imagine we apply (+1, 0) / (0, +1) actuator velocities. + const Eigen::Vector3d eefVelTrans = jacobian.block<3, 1>(0, column); // * 1.0 + + /* + * The rotation axis is orthogonal to the vector from origin to the + * EEF (eefStateTrans) and the movement direction (eefVelTrans). + * + * For the scaling, ask Cornelius. :) + */ + const Eigen::Vector3d scaledRotAxis = eefStateTrans.cross(eefVelTrans) + / eefStateTrans.norm() * 2; + + jacobian.block<3, 1>(3, column) = scaledRotAxis; // / 1.0; + } + } + + return jacobian; + } + + + Maths::ActuatorPosition Maths::angleToPosition(const Maths::ActuatorAngle& alpha) const + { + return lever * Eigen::sin((alpha + Eigen::Vector2d::Constant(theta0Rad)).array()); + } + +} diff --git a/VirtualRobot/Nodes/HemisphereJoint/Maths.h b/VirtualRobot/Nodes/HemisphereJoint/Maths.h new file mode 100644 index 0000000000000000000000000000000000000000..76f0188b727a41d6a24c236b29fde0eadaf2ee18 --- /dev/null +++ b/VirtualRobot/Nodes/HemisphereJoint/Maths.h @@ -0,0 +1,64 @@ +#pragma once + +#include <Eigen/Core> + +#include "Expressions.h" + + +namespace VirtualRobot::hemisphere +{ + + /** + * @brief Hemisphere joint mathematics for FK and IK. + * + * This class is a wrapper about `Expressions`, which contains the actual + * math code that was generated from Python sympy expressions + * using `python/hemisphere-joint-demo/hemisphere_joint_demo/sympy_to_code.py`. + */ + class Maths + { + public: + + using ActuatorPosition = Eigen::Vector2d; + using ActuatorAngle = Eigen::Vector2d; + using Jacobian = Eigen::Matrix<double, 6, 2>; + + public: + + Maths(); + Maths(double lever, double theta0); + + + void setConstants(double lever, double theta0); + + + void computeFkOfAngle(const ActuatorAngle& alpha12); + + void computeFkOfPosition(const ActuatorPosition& p12); + void computeFkOfPosition(double p1, double p2); + + + Eigen::Vector3d getEndEffectorTranslation() const; + Eigen::Matrix3d getEndEffectorRotation() const; + Eigen::Matrix4d getEndEffectorTransform() const; + + Jacobian getJacobian() const; + + ActuatorPosition angleToPosition(const ActuatorAngle& alpha) const; + + + public: + + double lever = 0; + double theta0Rad = 0; + double radius = 0; + + double limitLo = 0; + double limitHi = 0; + + + Expressions expressions; + + }; + +} diff --git a/VirtualRobot/Nodes/RobotNodeHemisphere.cpp b/VirtualRobot/Nodes/RobotNodeHemisphere.cpp index ef49ea6008ef9715494400000ab9b31f5b87beeb..a2d999867affb1b8adf6f5d79ffc12675ef5607b 100644 --- a/VirtualRobot/Nodes/RobotNodeHemisphere.cpp +++ b/VirtualRobot/Nodes/RobotNodeHemisphere.cpp @@ -10,12 +10,13 @@ #include <SimoxUtility/meta/enum/EnumNames.hpp> #include <SimoxUtility/math/pose/pose.h> +#include <SimoxUtility/math/convert/rad_to_deg.h> namespace VirtualRobot { - static const float limit = 1.0; + static const float initialLimit = 1.0; extern const simox::meta::EnumNames<RobotNodeHemisphere::Role> RoleNames = { @@ -35,10 +36,7 @@ namespace VirtualRobot RobotNodeHemisphere::RobotNodeHemisphere( RobotWeakPtr rob, const std::string& name, - float jointLimitLo, - float jointLimitHi, const Eigen::Matrix4f& preJointTransform, - const Eigen::Vector3f& axis, VisualizationNodePtr visualization, CollisionModelPtr collisionModel, float jointValueOffset, @@ -46,12 +44,9 @@ namespace VirtualRobot CollisionCheckerPtr colChecker, RobotNodeType type ) : - RobotNode(rob, name, -limit, limit, visualization, collisionModel, + RobotNode(rob, name, -initialLimit, initialLimit, visualization, collisionModel, jointValueOffset, physics, colChecker, type) { - (void) axis; - (void) jointLimitLo, (void) jointLimitHi; - initialized = false; optionalDHParameter.isSet = false; localTransformation = preJointTransform; @@ -62,8 +57,6 @@ namespace VirtualRobot RobotNodeHemisphere::RobotNodeHemisphere( RobotWeakPtr rob, const std::string& name, - float jointLimitLo, - float jointLimitHi, float a, float d, float alpha, float theta, VisualizationNodePtr visualization, CollisionModelPtr collisionModel, @@ -72,7 +65,7 @@ namespace VirtualRobot CollisionCheckerPtr colChecker, RobotNodeType type ) : - RobotNode(rob, name, jointLimitLo, jointLimitHi, visualization, collisionModel, + RobotNode(rob, name, -initialLimit, initialLimit, visualization, collisionModel, jointValueOffset, physics, colChecker, type) { initialized = false; @@ -109,16 +102,16 @@ namespace VirtualRobot void RobotNodeHemisphere::setXmlInfo(const XmlInfo& info) { - VR_ASSERT(second.has_value()); + VR_ASSERT(secondData.has_value()); switch (info.role) { case Role::FIRST: - first.emplace(First{}); - first->math.joint.setConstants(info.lever, info.theta0); + firstData.emplace(FirstData{}); + firstData->maths.maths.setConstants(info.lever, info.theta0Rad); break; case Role::SECOND: - second.emplace(Second{}); + secondData.emplace(SecondData{}); break; } } @@ -128,17 +121,18 @@ namespace VirtualRobot SceneObjectPtr parent, const std::vector<SceneObjectPtr>& children) { - VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(), std::stringstream() << first.has_value() << " / " << second.has_value()); + VR_ASSERT_MESSAGE(firstData.has_value() xor secondData.has_value(), + std::stringstream() << firstData.has_value() << " / " << secondData.has_value()); // The second node needs to store a reference to the first node. - if (second) + if (secondData) { - VR_ASSERT_MESSAGE(not second->first, "Second must not be initialized yet."); + VR_ASSERT_MESSAGE(not secondData->first, "Second must not be initialized yet."); RobotNodeHemisphere* firstNode = dynamic_cast<RobotNodeHemisphere*>(parent.get()); RobotNodeHemisphere* secondNode = this; - if (not (firstNode and firstNode->first)) + if (not (firstNode and firstNode->firstData)) { std::stringstream ss; ss << "The parent of a hemisphere joint with role '" << RoleNames.to_name(Role::SECOND) << "' " @@ -147,17 +141,18 @@ namespace VirtualRobot } // Save pointer to firstNode - second->first = firstNode; + secondData->firstNode = firstNode; + secondData->secondNode = secondNode; // Set up robot node parameters. { - const hemisphere::Joint& joint = second->math().joint; + const hemisphere::Maths& maths = secondData->maths().maths; - firstNode->jointLimitLo = joint.limitLo; - secondNode->jointLimitLo = joint.limitLo; + firstNode->jointLimitLo = maths.limitLo; + secondNode->jointLimitLo = maths.limitLo; - firstNode->jointLimitHi = joint.limitHi; - secondNode->jointLimitHi = joint.limitHi; + firstNode->jointLimitHi = maths.limitHi; + secondNode->jointLimitHi = maths.limitHi; } } @@ -165,49 +160,42 @@ namespace VirtualRobot } - void RobotNodeHemisphere::JointMath::update(const Eigen::Vector2f& actuators) - { - if (actuators != this->actuators) - { - joint.computeFkOfAngle(actuators.cast<double>()); - } - } - - void RobotNodeHemisphere::updateTransformationMatrices( const Eigen::Matrix4f& parentPose) { - VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(), std::stringstream() << first.has_value() << " / " << second.has_value()); + VR_ASSERT_MESSAGE(firstData.has_value() xor secondData.has_value(), std::stringstream() << firstData.has_value() << " / " << secondData.has_value()); - if (first) + if (firstData) { globalPose = parentPose * localTransformation; } - else if (second) + else if (secondData) { - VR_ASSERT_MESSAGE(second->first, "First node must be known to second node."); + VR_ASSERT_MESSAGE(secondData->first, "First node must be known to second node."); - JointMath& math = second->math(); - Eigen::Vector2f actuators(second->first->getJointValue(), this->getJointValue()); + hemisphere::CachedMaths& maths = secondData->maths(); + Eigen::Vector2f actuators(secondData->firstNode->getJointValue(), this->getJointValue()); - math.update(actuators); + maths.update(actuators); - Eigen::Vector3d translation = math.joint.getEndEffectorTranslation(); - const Eigen::Matrix3d rotation = math.joint.getEndEffectorRotation(); + Eigen::Vector3d translation = maths.maths.getEndEffectorTranslation(); + const Eigen::Matrix3d rotation = maths.maths.getEndEffectorRotation(); const Eigen::Matrix4d transform = simox::math::pose(translation, rotation); // Update Second - { - this->globalPose = parentPose * localTransformation * transform.cast<float>(); + this->globalPose = parentPose * localTransformation * transform.cast<float>(); + const bool verbose = false; + if (verbose) + { Eigen::IOFormat iof(5, 0, " ", "\n", " [", "]"); std::cout << __FUNCTION__ << "() of second actuator with " - << "\n lever = " << math.joint.lever - << "\n theta0 = " << math.joint.theta0 - << "\n radius = " << math.joint.radius + << "\n lever = " << maths.maths.lever + << "\n theta0 = " << maths.maths.theta0Rad + << "\n radius = " << maths.maths.radius << "\n joint value = " << jointValue << "\n actuator (angle) = \n" << actuators.transpose().format(iof) - << "\n actuator (pos) = \n" << math.joint.angleToPosition(actuators.cast<double>()).transpose().format(iof) + << "\n actuator (pos) = \n" << maths.maths.angleToPosition(actuators.cast<double>()).transpose().format(iof) << "\n local transform = \n" << localTransformation.format(iof) << "\n joint transform = \n" << transform.format(iof) << std::endl; @@ -219,7 +207,7 @@ namespace VirtualRobot void RobotNodeHemisphere::print(bool printChildren, bool printDecoration) const { ReadLockPtr lock = getRobot()->getReadLock(); - VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(), std::stringstream() << first.has_value() << " / " << second.has_value()); + VR_ASSERT_MESSAGE(firstData.has_value() xor secondData.has_value(), std::stringstream() << firstData.has_value() << " / " << secondData.has_value()); if (printDecoration) { @@ -228,14 +216,14 @@ namespace VirtualRobot RobotNode::print(false, false); - if (first) + if (firstData) { std::cout << "* Hemisphere joint first node"; } - else if (second) + else if (secondData) { std::cout << "* Hemisphere joint second node"; - std::cout << "* Transform: \n" << second->math().joint.getEndEffectorTransform() << std::endl; + std::cout << "* Transform: \n" << secondData->maths().maths.getEndEffectorTransform() << std::endl; } if (printDecoration) @@ -263,11 +251,11 @@ namespace VirtualRobot ReadLockPtr lock = getRobot()->getReadLock(); Physics physics = this->physics.scale(scaling); - RobotNodePtr result; + RobotNodeHemispherePtr cloned; if (optionalDHParameter.isSet) { - result.reset(new RobotNodeHemisphere( - newRobot, name, jointLimitLo, jointLimitHi, + cloned.reset(new RobotNodeHemisphere( + newRobot, name, optionalDHParameter.aMM() * scaling, optionalDHParameter.dMM() * scaling, optionalDHParameter.alphaRadian(), @@ -280,15 +268,24 @@ namespace VirtualRobot { Eigen::Matrix4f localTransform = getLocalTransformation(); simox::math::position(localTransform) *= scaling; - result.reset(new RobotNodeHemisphere( - newRobot, name, - jointLimitLo, jointLimitHi, - localTransform, Eigen::Vector3f::Zero(), + cloned.reset(new RobotNodeHemisphere( + newRobot, name, localTransform, visualizationModel, collisionModel, jointValueOffset, physics, colChecker, nodeType)); } - return result; + if (this->firstData) + { + // We can just copy the math object. + cloned->firstData = this->firstData; + } + else if (this->secondData) + { + cloned->secondData.emplace(SecondData{}); + // initialize() takes care of hooking up the second node to the first node. + } + + return cloned; } @@ -297,6 +294,27 @@ namespace VirtualRobot return true; } + bool RobotNodeHemisphere::isFirstHemisphereJointNode() const + { + return firstData.has_value(); + } + + bool RobotNodeHemisphere::isSecondHemisphereJointNode() const + { + return secondData.has_value(); + } + + const RobotNodeHemisphere::SecondData& RobotNodeHemisphere::getSecondData() const + { + VR_ASSERT(secondData.has_value()); + return secondData.value(); + } + + RobotNodeHemisphere::SecondData& RobotNodeHemisphere::getSecondData() + { + VR_ASSERT(secondData.has_value()); + return secondData.value(); + } void RobotNodeHemisphere::checkValidRobotNodeType() { @@ -307,35 +325,61 @@ namespace VirtualRobot std::string RobotNodeHemisphere::_toXML(const std::string& /*modelPath*/) { - VR_ASSERT_MESSAGE(first.has_value() xor second.has_value(), std::stringstream() << first.has_value() << " / " << second.has_value()); + VR_ASSERT_MESSAGE(firstData.has_value() xor secondData.has_value(), + std::stringstream() << firstData.has_value() << " / " << secondData.has_value()); - if (first) + std::stringstream ss; + ss << "\t\t<Joint type='Hemisphere'>" << std::endl; + if (firstData) { - // TODO - return ""; + // Constants are defined in first. + + hemisphere::Maths& maths = firstData->maths.maths; + ss << "\t\t\t<hemisphere role='first'" + << " lever='" << maths.lever << "'" + << " theta0='" << simox::math::rad_to_deg(maths.theta0Rad) << "'" + << " />" << std::endl; } else { - JointMath& math = second->math(); - - std::stringstream ss; - ss << "\t\t<Joint type='Hemisphere'>" << std::endl; - ss << "\t\t\t<hemisphere lever='" << math.joint.lever << "' theta0='" << math.joint.theta0 << "' />" << std::endl; - ss << "\t\t\t<limits lo='" << jointLimitLo << "' hi='" << jointLimitHi << "' units='radian'/>" << std::endl; - ss << "\t\t\t<MaxAcceleration value='" << maxAcceleration << "'/>" << std::endl; - ss << "\t\t\t<MaxVelocity value='" << maxVelocity << "'/>" << std::endl; - ss << "\t\t\t<MaxTorque value='" << maxTorque << "'/>" << std::endl; - std::map< std::string, float >::iterator propIt = propagatedJointValues.begin(); - - while (propIt != propagatedJointValues.end()) - { - ss << "\t\t\t<PropagateJointValue name='" << propIt->first << "' factor='" << propIt->second << "'/>" << std::endl; - propIt++; - } + ss << "\t\t\t<hemisphere role='second' />" << std::endl; + } + + ss << "\t\t\t<MaxAcceleration value='" << maxAcceleration << "'/>" << std::endl; + ss << "\t\t\t<MaxVelocity value='" << maxVelocity << "'/>" << std::endl; + ss << "\t\t\t<MaxTorque value='" << maxTorque << "'/>" << std::endl; - ss << "\t\t</Joint>" << std::endl; - return ss.str(); + for (auto propIt = propagatedJointValues.begin(); propIt != propagatedJointValues.end(); ++propIt) + { + ss << "\t\t\t<PropagateJointValue name='" << propIt->first << "' factor='" << propIt->second << "'/>" << std::endl; } + + ss << "\t\t</Joint>" << std::endl; + return ss.str(); + } + + hemisphere::CachedMaths& RobotNodeHemisphere::SecondData::maths() + { + return firstNode->firstData->maths; + } + + const hemisphere::CachedMaths& RobotNodeHemisphere::SecondData::maths() const + { + return firstNode->firstData->maths; + } + + hemisphere::Maths::Jacobian RobotNodeHemisphere::SecondData::getJacobian() + { + VR_ASSERT(firstNode); + VR_ASSERT(secondNode); + + hemisphere::CachedMaths& maths = this->maths(); + + Eigen::Vector2f actuators(firstNode->getJointValue(), secondNode->getJointValue()); + maths.update(actuators); + + hemisphere::Maths::Jacobian jacobian = maths.maths.getJacobian(); + return jacobian; } } diff --git a/VirtualRobot/Nodes/RobotNodeHemisphere.h b/VirtualRobot/Nodes/RobotNodeHemisphere.h index 7a548e60075203990e86d891f6bad4e511e242e8..73c7e8b617fe9fd5be26ae83563fc59df47195ce 100644 --- a/VirtualRobot/Nodes/RobotNodeHemisphere.h +++ b/VirtualRobot/Nodes/RobotNodeHemisphere.h @@ -21,10 +21,11 @@ */ #pragma once -#include "../VirtualRobot.h" +#include <VirtualRobot/VirtualRobot.h> -#include "RobotNode.h" -#include "HemisphereJoint/Joint.h" +#include <VirtualRobot/Nodes/RobotNode.h> +#include <VirtualRobot/Nodes/HemisphereJoint/CachedMaths.h> +#include <VirtualRobot/Nodes/HemisphereJoint/Maths.h> #include <Eigen/Core> @@ -38,38 +39,72 @@ namespace VirtualRobot using RobotNodeHemispherePtr = std::shared_ptr<class RobotNodeHemisphere>; + /** + * @brief A model of the 2 DoF wrist joint mechanism published in: + * + * C. Klas and T. Asfour, "A Compact, Lightweight and Singularity-Free + * Wrist Joint Mechanism for Humanoid Robots," 2022 IEEE/RSJ International + * Conference on Intelligent Robots and Systems (IROS), Kyoto, Japan, 2022, + * pp. 457-464, doi: 10.1109/IROS47612.2022.9981787. + * + * This joint mechanism represents 2 Degrees of Freedom (DoF). Therefore, + * it must be represented by two robot nodes in VirtualRobot. + */ class VIRTUAL_ROBOT_IMPORT_EXPORT RobotNodeHemisphere : public RobotNode { public: enum class Role { + /// The first DoF in the kinematic chain. FIRST, + /// The second DoF in the kinematic chain. SECOND, }; static Role RoleFromString(const std::string& string); + /// Information specified in the robot XML. struct XmlInfo { Role role; // Only set for first: - double theta0 = -1; + double theta0Rad = -1; double lever = -1; }; + + /// Data held by the first joint. + struct FirstData + { + hemisphere::CachedMaths maths; + }; + + /// Data held by the second joint. + struct SecondData + { + /// The first actuator node. + RobotNodeHemisphere* firstNode = nullptr; + RobotNodeHemisphere* secondNode = nullptr; + + hemisphere::CachedMaths& maths(); + const hemisphere::CachedMaths& maths() const; + + hemisphere::Maths::Jacobian getJacobian(); + }; + friend class RobotFactory; + + public: + EIGEN_MAKE_ALIGNED_OPERATOR_NEW RobotNodeHemisphere( RobotWeakPtr rob, ///< The robot const std::string& name, ///< The name - float jointLimitLo, ///< lower joint limit - float jointLimitHi, ///< upper joint limit const Eigen::Matrix4f& preJointTransform, ///< This transformation is applied before the translation of the joint is done - const Eigen::Vector3f& axis, ///< The rotation axis (in local joint coord system) VisualizationNodePtr visualization = nullptr, ///< A visualization model CollisionModelPtr collisionModel = nullptr, ///< A collision model float jointValueOffset = 0.0f, ///< An offset that is internally added to the joint value @@ -78,11 +113,10 @@ namespace VirtualRobot RobotNodeType type = Generic ); + // The DH-based constructor is not tested so far for Hemisphere joints. RobotNodeHemisphere( RobotWeakPtr rob, ///< The robot const std::string& name, ///< The name - float jointLimitLo, ///< lower joint limit - float jointLimitHi, ///< upper joint limit float a, ///< dh paramters float d, ///< dh paramters float alpha, ///< dh paramters @@ -118,6 +152,15 @@ namespace VirtualRobot bool isHemisphereJoint() const override; + bool isFirstHemisphereJointNode() const; + bool isSecondHemisphereJointNode() const; + + /** + * @brief Get the data held by the second node. + * May only be called if `isFirstHemisphereJointNode()`; + */ + SecondData& getSecondData(); + const SecondData& getSecondData() const; protected: @@ -140,48 +183,18 @@ namespace VirtualRobot ) override; RobotNodePtr - _clone( - const RobotPtr newRobot, - const VisualizationNodePtr visualizationModel, - const CollisionModelPtr collisionModel, - CollisionCheckerPtr colChecker, - float scaling - ) override; + _clone(const RobotPtr newRobot, + const VisualizationNodePtr visualizationModel, + const CollisionModelPtr collisionModel, + CollisionCheckerPtr colChecker, + float scaling + ) override; - protected: - - struct JointMath - { - /// The actuator values that were used to compute the joint math. - Eigen::Vector2f actuators = Eigen::Vector2f::Constant(std::numeric_limits<float>::min()); - /// The joint math. - hemisphere::Joint joint; - - void update(const Eigen::Vector2f& actuators); - }; + private: - struct First - { - JointMath math; - }; - std::optional<First> first; - - struct Second - { - /// The first actuator node. - RobotNodeHemisphere* first = nullptr; - - JointMath& math() - { - return first->first->math; - } - const JointMath& math() const - { - return first->first->math; - } - }; - std::optional<Second> second; + std::optional<FirstData> firstData; + std::optional<SecondData> secondData; }; diff --git a/VirtualRobot/Nodes/RobotNodeHemisphereFactory.cpp b/VirtualRobot/Nodes/RobotNodeHemisphereFactory.cpp index 0fade1edc79f45ab2d743d9ed1e9fede66b1946d..52b590d7cab045586daf2a699663d8e4800ba428 100644 --- a/VirtualRobot/Nodes/RobotNodeHemisphereFactory.cpp +++ b/VirtualRobot/Nodes/RobotNodeHemisphereFactory.cpp @@ -7,7 +7,8 @@ #include "RobotNodeHemisphereFactory.h" #include "RobotNode.h" #include "RobotNodeHemisphere.h" -#include "../CollisionDetection/CollisionModel.h" + +#include <VirtualRobot/CollisionDetection/CollisionModel.h> namespace VirtualRobot @@ -21,7 +22,8 @@ namespace VirtualRobot = default; - RobotNodePtr RobotNodeHemisphereFactory::createRobotNode( + RobotNodePtr + RobotNodeHemisphereFactory::createRobotNode( RobotPtr robot, const std::string& nodeName, VisualizationNodePtr visualizationModel, @@ -34,25 +36,26 @@ namespace VirtualRobot const Eigen::Vector3f& /*translationDirection*/, const SceneObject::Physics& physics, RobotNode::RobotNodeType rntype) const + { - std::cout << "CREATE NEW HEMISPHERE JOINT" << std::endl; + (void) limitLow, (void) limitHigh; + (void) axis; + return std::make_shared<RobotNodeHemisphere>( robot, nodeName, - limitLow, - limitHigh, preJointTransform, - axis, visualizationModel, collisionModel, jointValueOffset, physics, - (collisionModel ? collisionModel->getCollisionChecker() : CollisionCheckerPtr()), + collisionModel ? collisionModel->getCollisionChecker() : CollisionCheckerPtr(), rntype); } - RobotNodePtr RobotNodeHemisphereFactory::createRobotNodeDH( + RobotNodePtr + RobotNodeHemisphereFactory::createRobotNodeDH( RobotPtr robot, const std::string& nodeName, VisualizationNodePtr visualizationModel, @@ -64,12 +67,11 @@ namespace VirtualRobot const SceneObject::Physics& physics, RobotNode::RobotNodeType rntype) const { - std::cout << "CREATE NEW HEMISPHERE JOINT DH" << std::endl; + (void) limitLow, (void) limitHigh; + return std::make_shared<RobotNodeHemisphere>( robot, nodeName, - limitLow, - limitHigh, dhParameters.aMM(), dhParameters.dMM(), dhParameters.alphaRadian(), @@ -78,21 +80,24 @@ namespace VirtualRobot collisionModel, jointValueOffset, physics, - CollisionCheckerPtr(), + collisionModel ? collisionModel->getCollisionChecker() : CollisionCheckerPtr(), rntype); } - RobotNodeFactory::SubClassRegistry RobotNodeHemisphereFactory::registry(RobotNodeHemisphereFactory::getName(), &RobotNodeHemisphereFactory::createInstance); + RobotNodeFactory::SubClassRegistry + RobotNodeHemisphereFactory::registry(RobotNodeHemisphereFactory::getName(), &RobotNodeHemisphereFactory::createInstance); - std::string RobotNodeHemisphereFactory::getName() + std::string + RobotNodeHemisphereFactory::getName() { return "hemisphere"; } - std::shared_ptr<RobotNodeFactory> RobotNodeHemisphereFactory::createInstance(void*) + std::shared_ptr<RobotNodeFactory> + RobotNodeHemisphereFactory::createInstance(void*) { return std::make_shared<RobotNodeHemisphereFactory>(); } diff --git a/VirtualRobot/XML/RobotIO.cpp b/VirtualRobot/XML/RobotIO.cpp index 6b5808efc0744a4f3cb575a71c5901a2cfc3957e..b4f1a4c34c0c067645393b9b4a8efc80ccb0cc76 100644 --- a/VirtualRobot/XML/RobotIO.cpp +++ b/VirtualRobot/XML/RobotIO.cpp @@ -467,7 +467,7 @@ namespace VirtualRobot { case RobotNodeHemisphere::Role::FIRST: hemisphere->lever = getFloatByAttributeName(node, "lever"); - hemisphere->theta0 = simox::math::deg_to_rad(getFloatByAttributeName(node, "theta0")); + hemisphere->theta0Rad = simox::math::deg_to_rad(getFloatByAttributeName(node, "theta0")); break; case RobotNodeHemisphere::Role::SECOND: break; diff --git a/VirtualRobot/data/robots/examples/HemisphereJoint/Joint3Hemisphere.xml b/VirtualRobot/data/robots/examples/HemisphereJoint/Joint3Hemisphere.xml index 7cdabb74a2d65856611b5a7f909a84ea42811026..4b4ac38761e6f15152e6b4d7b475658bb66f3e83 100644 --- a/VirtualRobot/data/robots/examples/HemisphereJoint/Joint3Hemisphere.xml +++ b/VirtualRobot/data/robots/examples/HemisphereJoint/Joint3Hemisphere.xml @@ -1,26 +1,26 @@ <?xml version="1.0" encoding="UTF-8" ?> <Robot Type="Simple3DoFRobotWithHemisphereJoint" StandardName="Simple3DoFRobotWithHemisphereJoint" RootNode="First"> - + <RobotNode name="First"> <Visualization enable="true"> - <CoordinateAxis enable="true" scaling="1" text="Root"/> + <CoordinateAxis enable="true" scaling="1" text="Root"/> <File type="Inventor">end.iv</File> - </Visualization> + </Visualization> <Child name="Joint1_Revolute"/> - </RobotNode> + </RobotNode> <RobotNode name="Joint1_Revolute"> - <Joint type="revolute"> - <Limits unit="degree" lo="-90" hi="90"/> + <Joint type="revolute"> + <Limits unit="degree" lo="-90" hi="90"/> <Axis x="1" y="0" z="0"/> </Joint> - <Visualization enable="true"> + <Visualization enable="true"> <File type="Inventor">joint_01_base.iv</File> - <UseAsCollisionModel/> - </Visualization> + <UseAsCollisionModel/> + </Visualization> <Child name="Joint2_Hemisphere_A"/> - </RobotNode> + </RobotNode> <RobotNode name="Joint2_Hemisphere_A"> <Transform> @@ -31,10 +31,10 @@ </Joint> <Visualization enable="true"> <File type="Inventor">joint_02_hemisphere_a.iv</File> - <UseAsCollisionModel/> + <UseAsCollisionModel/> </Visualization> <Child name="Joint2_Hemisphere_B"/> - </RobotNode> + </RobotNode> <RobotNode name="Joint2_Hemisphere_B"> <Joint type="hemisphere"> @@ -48,44 +48,49 @@ </RobotNode> <RobotNode name="Joint3_Revolute"> - <Transform> + <Transform> <Translation x="0" y="0" z="50" /> - </Transform> + </Transform> <Joint type="revolute"> <Axis x="1" y="0" z="0"/> - <Limits unit="degree" lo="-90" hi="90"/> + <Limits unit="degree" lo="-90" hi="90"/> </Joint> - <Visualization enable="true"> + <Visualization enable="true"> <File type="Inventor">joint_03_finger.iv</File> - <UseAsCollisionModel/> - </Visualization> + <UseAsCollisionModel/> + </Visualization> <Child name="Last"/> - </RobotNode> + </RobotNode> <RobotNode name="Last"> - <Transform> + <Transform> <Translation x="0" y="0" z="300" /> - </Transform> - <Visualization enable="true"> + </Transform> + <Visualization enable="true"> <File type="Inventor">end.iv</File> - <UseAsCollisionModel/> - </Visualization> - </RobotNode> + <UseAsCollisionModel/> + </Visualization> + </RobotNode> <RobotNodeSet name="AllJoints" kinematicRoot="Joint1_Revolute"> <Node name="Joint1_Revolute"/> <Node name="Joint2_Hemisphere_A"/> <Node name="Joint2_Hemisphere_B"/> <Node name="Joint3_Revolute"/> - </RobotNodeSet> + </RobotNodeSet> + + <RobotNodeSet name="HemisphereJoints" kinematicRoot="Joint2_Hemisphere_A" tcp="Last"> + <Node name="Joint2_Hemisphere_A"/> + <Node name="Joint2_Hemisphere_B"/> + </RobotNodeSet> - <RobotNodeSet name="CollisionModel"> + <RobotNodeSet name="CollisionModel"> <Node name="First"/> <Node name="Joint1_Revolute"/> <Node name="Joint2_Hemisphere_A"/> <Node name="Joint2_Hemisphere_B"/> <Node name="Joint3_Revolute"/> <Node name="Last"/> - </RobotNodeSet> + </RobotNodeSet> </Robot> diff --git a/VirtualRobot/examples/HemisphereJoint/main.cpp b/VirtualRobot/examples/HemisphereJoint/main.cpp index 46693a0c20edf8a282074eb70026189f9b75676d..73f38a97a5c1aa15fbd01bb68a30877d81f14322 100644 --- a/VirtualRobot/examples/HemisphereJoint/main.cpp +++ b/VirtualRobot/examples/HemisphereJoint/main.cpp @@ -6,20 +6,17 @@ #include <SimoxUtility/math/statistics/measures.h> #include <VirtualRobot/RuntimeEnvironment.h> -#include <VirtualRobot/Nodes/HemisphereJoint/Joint.h> +#include <VirtualRobot/Nodes/HemisphereJoint/Maths.h> using VirtualRobot::RuntimeEnvironment; -/** - * - */ int main(int argc, char* argv[]) { const Eigen::IOFormat iof(5, 0, " ", "\n", " [", "]", "", ""); - RuntimeEnvironment::setCaption("Convert .iv to .wrl files"); + RuntimeEnvironment::setCaption("Compute hemisphere joint mathematics"); RuntimeEnvironment::considerFlag( "verbose", "Enable verbose output."); @@ -67,7 +64,7 @@ int main(int argc, char* argv[]) { const time_point start = std::chrono::system_clock::now(); - VirtualRobot::hemisphere::Joint joint; + VirtualRobot::hemisphere::Maths joint; joint.computeFkOfPosition(a1, a2); const Eigen::Vector3d pos = joint.getEndEffectorTranslation(); @@ -109,7 +106,7 @@ int main(int argc, char* argv[]) a1 += offset; a2 += offset; - VirtualRobot::hemisphere::Joint joint; + VirtualRobot::hemisphere::Maths joint; joint.computeFkOfPosition(a1, a2); const Eigen::Vector3d pos = joint.getEndEffectorTranslation();