#include "PoseAxesBuilder.h" #include "CoordinateTransform.h" #include "DetectPresenter.h" // for HandEyeExtrinsic #include "JiuruiWorkpiecePoseTCPProtocol.h" // for RobotPose6D #include #ifndef M_PI #define M_PI 3.14159265358979323846 #endif namespace { constexpr double kVectorEpsilon = 1e-8; constexpr double kRotationEpsilon = 1e-9; double DotProduct(const CTVec3D& a, const CTVec3D& b) { return a.x * b.x + a.y * b.y + a.z * b.z; } CTVec3D CrossProduct(const CTVec3D& a, const CTVec3D& b) { return CTVec3D(a.y * b.z - a.z * b.y, a.z * b.x - a.x * b.z, a.x * b.y - a.y * b.x); } CTVec3D ToCTVec3D(const SVzNL3DPoint& point) { return CTVec3D(point.x, point.y, point.z); } double WrapDegreesTo180(double deg) { while (deg > 180.0) { deg -= 360.0; } while (deg <= -180.0) { deg += 360.0; } return deg; } // 对 Tait-Bryan 欧拉角做等价归一化,使 pitch 属于 [-90 度, 90 度]。 // 依据:(alpha, beta, gamma) 等价于 (alpha +/- 180 度, 180 度 - beta, gamma +/- 180 度) 以及 (alpha +/- 180 度, -180 度 - beta, gamma +/- 180 度), // 适用于 CTEulerOrder 中所有 12 种 Tait-Bryan 顺序(内旋/外旋一致)。 void NormalizePitchRange(double& rollDeg, double& pitchDeg, double& yawDeg) { pitchDeg = WrapDegreesTo180(pitchDeg); if (pitchDeg > 90.0) { pitchDeg = 180.0 - pitchDeg; rollDeg += 180.0; yawDeg += 180.0; } else if (pitchDeg < -90.0) { pitchDeg = -180.0 - pitchDeg; rollDeg += 180.0; yawDeg += 180.0; } rollDeg = WrapDegreesTo180(rollDeg); yawDeg = WrapDegreesTo180(yawDeg); } // 万向锁消歧:当 |pitch| 接近 90 度 时,Rz(yaw).Ry(pitch).Rx(roll) 的分解退化成一个自由度。 // 严格展开可得: // pitch = +90 度 -> R 只依赖 (roll - yaw);任意满足 roll - yaw = const 的组合都表示同一 R // pitch = -90 度 -> R 只依赖 (roll + yaw);任意满足 roll + yaw = const 的组合都表示同一 R // 本函数把 yaw 锚定到 refYaw 附近,把剩余自由度放到 roll,让不同帧的输出稳定落在 // 参考(通常是机器人法兰的 rx/rz)附近。阈值覆盖 80 度 - 85 度: // |pitch| < 80 度:完全不改(保留真实分解) // |pitch| >= 85 度:完全锁到参考(对 R 影响最小) // 80 度 <= |pitch| < 85 度:线性插值过渡 void ResolveGimbalAmbiguity(double& rollDeg, double pitchDeg, double& yawDeg, double refRollDeg, double refYawDeg) { constexpr double kSoftThresholdDeg = 80.0; constexpr double kHardThresholdDeg = 85.0; const double absPitch = std::abs(pitchDeg); if (absPitch < kSoftThresholdDeg) { return; } // 不变量方向与 pitch 符号的关系(由 Rz.Ry.Rx 展开式导出): // pitch > 0 -> inv = roll - yaw // pitch < 0 -> inv = roll + yaw const bool positivePitch = (pitchDeg >= 0.0); const double sum = positivePitch ? (rollDeg - yawDeg) : (rollDeg + yawDeg); const double refSum = positivePitch ? (refRollDeg - refYawDeg) : (refRollDeg + refYawDeg); double adjustedSum = sum; while (adjustedSum - refSum > 180.0) { adjustedSum -= 360.0; } while (adjustedSum - refSum < -180.0) { adjustedSum += 360.0; } const double fullYaw = WrapDegreesTo180(refYawDeg); const double fullRoll = WrapDegreesTo180(positivePitch ? (adjustedSum + fullYaw) : (adjustedSum - fullYaw)); if (absPitch >= kHardThresholdDeg) { rollDeg = fullRoll; yawDeg = fullYaw; return; } const double alpha = (absPitch - kSoftThresholdDeg) / (kHardThresholdDeg - kSoftThresholdDeg); auto lerpAngle = [alpha](double from, double to) { const double diff = WrapDegreesTo180(to - from); return WrapDegreesTo180(from + alpha * diff); }; rollDeg = lerpAngle(rollDeg, fullRoll); yawDeg = lerpAngle(yawDeg, fullYaw); } // 从单一法向量 Z 轴构造完整正交三元组(X/Y 自动补全)。 // 用于 NormalDirToPoseAngles:算法只给出孔的法向量,需要补全姿态。 bool BuildFrameFromZAxis(const CTVec3D& zAxisInput, std::array& axes) { const CTVec3D zAxis = PoseAxesBuilder::NormalizeVector(zAxisInput); if (!PoseAxesBuilder::IsValidVector(zAxis)) { return false; } const CTVec3D worldY(0.0, 1.0, 0.0); const CTVec3D worldX(1.0, 0.0, 0.0); // 尝试 Z x worldY 得到 X 轴 CTVec3D xAxis = PoseAxesBuilder::NormalizeVector(CrossProduct(zAxis, worldY)); if (!PoseAxesBuilder::IsValidVector(xAxis)) { // Z 轴与 worldY 近共线,改用 worldX xAxis = PoseAxesBuilder::NormalizeVector(CrossProduct(zAxis, worldX)); } if (!PoseAxesBuilder::IsValidVector(xAxis)) { return false; } const CTVec3D yAxis = PoseAxesBuilder::NormalizeVector(CrossProduct(zAxis, xAxis)); if (!PoseAxesBuilder::IsValidVector(yAxis)) { return false; } axes = {xAxis, yAxis, zAxis}; return true; } // 公共手眼变换构建:从 clibMatrix + robotPose + extrinsic 产出一个 eyeInHandTransform, // 同时返回 order / rxDeg / rzDeg 供后续欧拉提取和万向锁消歧使用。 void BuildEyeInHandTransform(const double matrix16[16], const RobotPose6D& robotPose, const HandEyeExtrinsic& extrinsic, int poseOutputOrder, CTHomogeneousMatrix& eyeInHandTransform, CTEulerOrder& order, double& rxDeg, double& rzDeg) { const CTHomogeneousMatrix handEyeMatrix = PoseAxesBuilder::BuildHandEyeMatrix(matrix16); order = static_cast(extrinsic.eulerOrder); double ryDeg = 0.0; PoseAxesBuilder::ResolveRobotPoseAnglesDegrees(robotPose, poseOutputOrder, rxDeg, ryDeg, rzDeg); const CTRobotPose flangePose = CTRobotPose::fromDegrees(robotPose.x, robotPose.y, robotPose.z, rxDeg, ryDeg, rzDeg); eyeInHandTransform = CCoordinateTransform::sixAxisEyeInHandBuildTransform(flangePose, order, handEyeMatrix); } // 填充输出位姿:将 Eye 系点经手眼变换后加上 offset,封装欧拉角。 void FillOutputPose(const CTVec3D& eyePoint, const CTHomogeneousMatrix& eyeInHandTransform, const HandEyeExtrinsic& extrinsic, const PoseAxesBuilder::PoseAngles& angles, double& outX, double& outY, double& outZ, double& outRoll, double& outPitch, double& outYaw) { const CTVec3D robotPoint = eyeInHandTransform.transformPoint(eyePoint); outX = robotPoint.x + extrinsic.offsetX; outY = robotPoint.y + extrinsic.offsetY; outZ = robotPoint.z + extrinsic.offsetZ; outRoll = angles.rollDeg; outPitch = angles.pitchDeg; outYaw = angles.yawDeg; } } // namespace namespace PoseAxesBuilder { CTVec3D NormalizeVector(const CTVec3D& v) { const double length = v.norm(); if (length < kVectorEpsilon) { return CTVec3D(); } return v * (1.0 / length); } bool IsValidVector(const CTVec3D& v) { return v.norm() >= kVectorEpsilon; } CTHomogeneousMatrix BuildHandEyeMatrix(const double matrix16[16]) { CTHomogeneousMatrix handEyeMatrix; for (int row = 0; row < 4; ++row) { for (int col = 0; col < 4; ++col) { handEyeMatrix.at(row, col) = matrix16[row * 4 + col]; } } return handEyeMatrix; } void ResolveRobotPoseAnglesDegrees(const RobotPose6D& robotPose, int poseOutputOrder, double& rxDeg, double& ryDeg, double& rzDeg) { switch (poseOutputOrder) { case 1: rxDeg = robotPose.a; ryDeg = robotPose.b; rzDeg = robotPose.c; break; case 2: rxDeg = robotPose.b; ryDeg = robotPose.a; rzDeg = robotPose.c; break; case 3: rxDeg = robotPose.c; ryDeg = robotPose.a; rzDeg = robotPose.b; break; case 4: rxDeg = robotPose.b; ryDeg = robotPose.c; rzDeg = robotPose.a; break; case 5: rxDeg = robotPose.c; ryDeg = robotPose.b; rzDeg = robotPose.a; break; case 0: default: rxDeg = robotPose.a; ryDeg = robotPose.b; rzDeg = robotPose.c; break; } } bool BuildAnchoredFrame(const CTVec3D& primary, const CTVec3D& referenceY, std::array& axes, double minPerpendicularity) { const CTVec3D xAxis = NormalizeVector(primary); if (!IsValidVector(xAxis)) { return false; } const CTVec3D refY = NormalizeVector(referenceY); if (!IsValidVector(refY)) { return false; } // Gram-Schmidt:refY 投影到与 X 垂直的平面。 // |yRaw| = sin(refY 与 X 的夹角)。小于阈值则两者近共线、副轴方向无意义。 const CTVec3D yRaw = refY - xAxis * DotProduct(refY, xAxis); if (yRaw.norm() < minPerpendicularity) { return false; } const CTVec3D yAxis = NormalizeVector(yRaw); const CTVec3D zAxis = NormalizeVector(CrossProduct(xAxis, yAxis)); if (!IsValidVector(zAxis)) { return false; } axes = {xAxis, yAxis, zAxis}; return true; } void ApplyAxesRotation(std::array& axes, double rotXDeg, double rotYDeg, double rotZDeg) { if (std::fabs(rotXDeg) < kRotationEpsilon && std::fabs(rotYDeg) < kRotationEpsilon && std::fabs(rotZDeg) < kRotationEpsilon) { return; } const double rx = rotXDeg * M_PI / 180.0; const double ry = rotYDeg * M_PI / 180.0; const double rz = rotZDeg * M_PI / 180.0; const double cx = std::cos(rx), sx = std::sin(rx); const double cy = std::cos(ry), sy = std::sin(ry); const double cz = std::cos(rz), sz = std::sin(rz); // R = Rx(rx) * Ry(ry) * Rz(rz);对工具三轴(列向量)右乘 R,等价于在工具自身坐标系 // 内按内旋 XYZ 顺序施加补偿旋转。 double R[3][3]; R[0][0] = cy * cz; R[0][1] = -cy * sz; R[0][2] = sy; R[1][0] = sx * sy * cz + cx * sz; R[1][1] = -sx * sy * sz + cx * cz; R[1][2] = -sx * cy; R[2][0] = -cx * sy * cz + sx * sz; R[2][1] = cx * sy * sz + sx * cz; R[2][2] = cx * cy; const CTVec3D oldX = axes[0]; const CTVec3D oldY = axes[1]; const CTVec3D oldZ = axes[2]; axes[0] = oldX * R[0][0] + oldY * R[1][0] + oldZ * R[2][0]; axes[1] = oldX * R[0][1] + oldY * R[1][1] + oldZ * R[2][1]; axes[2] = oldX * R[0][2] + oldY * R[1][2] + oldZ * R[2][2]; } bool TransformAxes(const std::array& srcAxes, const CTHomogeneousMatrix& transform, std::array& dstAxes) { for (size_t i = 0; i < srcAxes.size(); ++i) { dstAxes[i] = NormalizeVector(transform.transformVector(srcAxes[i])); if (!IsValidVector(dstAxes[i])) { return false; } } return true; } CTRotationMatrix BuildRotationMatrix(const std::array& axes) { CTRotationMatrix rotation; rotation.at(0, 0) = axes[0].x; rotation.at(0, 1) = axes[1].x; rotation.at(0, 2) = axes[2].x; rotation.at(1, 0) = axes[0].y; rotation.at(1, 1) = axes[1].y; rotation.at(1, 2) = axes[2].y; rotation.at(2, 0) = axes[0].z; rotation.at(2, 1) = axes[1].z; rotation.at(2, 2) = axes[2].z; return rotation; } void RotationMatrixToConfiguredEulerDegrees(const CTRotationMatrix& rotation, CTEulerOrder order, double& rollDeg, double& pitchDeg, double& yawDeg) { const CTEulerAngles euler = CCoordinateTransform::rotationMatrixToEuler(rotation, order); euler.toDegrees(rollDeg, pitchDeg, yawDeg); NormalizePitchRange(rollDeg, pitchDeg, yawDeg); } bool ComputeRobotPoseAngles(const std::array& eyeAxes, const CTHomogeneousMatrix& eyeInHandTransform, const HandEyeExtrinsic& extrinsic, CTEulerOrder eulerOrder, double refRollDeg, double refYawDeg, PoseAngles& outAngles) { std::array eyeAxesWorking = eyeAxes; // 1) Eye 系内补偿 ApplyAxesRotation(eyeAxesWorking, extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ); // 2) Eye -> Robot 变换 std::array robotAxes; if (!TransformAxes(eyeAxesWorking, eyeInHandTransform, robotAxes)) { return false; } // 3) Robot 系内补偿 ApplyAxesRotation(robotAxes, extrinsic.outRotX, extrinsic.outRotY, extrinsic.outRotZ); // 4) 提欧拉角 + 万向锁消歧 const CTRotationMatrix robotRotation = BuildRotationMatrix(robotAxes); RotationMatrixToConfiguredEulerDegrees(robotRotation, eulerOrder, outAngles.rollDeg, outAngles.pitchDeg, outAngles.yawDeg); ResolveGimbalAmbiguity(outAngles.rollDeg, outAngles.pitchDeg, outAngles.yawDeg, refRollDeg, refYawDeg); return true; } // ============ 工件孔定位专用工具 ============ bool PointPoseToEuler(const SSG_pointPose& pointPose, const double handEyeMatrix[16], const HandEyeExtrinsic& extrinsic, const RobotPose6D& robotPose, double& outX, double& outY, double& outZ, double& outRoll, double& outPitch, double& outYaw, int poseOutputOrder) { // 从 SSG_pointPose 的三轴向量构造 Eye 系三元组 std::array eyeAxes; const CTVec3D eyeXAxis = NormalizeVector(ToCTVec3D(pointPose.pose_x)); const CTVec3D eyeYAxis = NormalizeVector(ToCTVec3D(pointPose.pose_y)); if (!BuildAnchoredFrame(eyeXAxis, eyeYAxis, eyeAxes)) { return false; } // 构建手眼变换 CTHomogeneousMatrix eyeInHandTransform; CTEulerOrder order = CTEulerOrder::sZYX; double rxDeg = 0.0; double rzDeg = 0.0; BuildEyeInHandTransform(handEyeMatrix, robotPose, extrinsic, poseOutputOrder, eyeInHandTransform, order, rxDeg, rzDeg); // 计算欧拉角 PoseAngles angles; if (!ComputeRobotPoseAngles(eyeAxes, eyeInHandTransform, extrinsic, order, rxDeg, rzDeg, angles)) { return false; } // 填充输出 const CTVec3D eyePoint = ToCTVec3D(pointPose.point); FillOutputPose(eyePoint, eyeInHandTransform, extrinsic, angles, outX, outY, outZ, outRoll, outPitch, outYaw); return true; } bool NormalDirToPoseAngles(const SVzNL3DPoint& center, const SVzNL3DPoint& normDir, const double handEyeMatrix[16], const HandEyeExtrinsic& extrinsic, const RobotPose6D& robotPose, double& outX, double& outY, double& outZ, double& outRoll, double& outPitch, double& outYaw, int poseOutputOrder) { // 从单一法向量构造 Eye 系三元组(Z = normDir,X/Y 自动补全) std::array eyeAxes; if (!BuildFrameFromZAxis(ToCTVec3D(normDir), eyeAxes)) { return false; } // 构建手眼变换 CTHomogeneousMatrix eyeInHandTransform; CTEulerOrder order = CTEulerOrder::sZYX; double rxDeg = 0.0; double rzDeg = 0.0; BuildEyeInHandTransform(handEyeMatrix, robotPose, extrinsic, poseOutputOrder, eyeInHandTransform, order, rxDeg, rzDeg); // 计算欧拉角 PoseAngles angles; if (!ComputeRobotPoseAngles(eyeAxes, eyeInHandTransform, extrinsic, order, rxDeg, rzDeg, angles)) { return false; } // 填充输出 const CTVec3D eyePoint = ToCTVec3D(center); FillOutputPose(eyePoint, eyeInHandTransform, extrinsic, angles, outX, outY, outZ, outRoll, outPitch, outYaw); return true; } } // namespace PoseAxesBuilder