361 lines
12 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include "PoseAxesBuilder.h"
#include "CoordinateTransform.h"
#include "DetectPresenter.h" // for HandEyeExtrinsic
#include "ScrewPositionTCPProtocol.h" // for RobotPose6D
#include <cmath>
#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);
}
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°]。
// 依据:(α, β, γ) ≡ (α±180°, 180°-β, γ±180°) 以及 (α±180°, -180°-β, γ±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);
}
} // 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 clibMatrix[16])
{
CTHomogeneousMatrix handEyeMatrix;
for (int row = 0; row < 4; ++row) {
for (int col = 0; col < 4; ++col) {
handEyeMatrix.at(row, col) = clibMatrix[row * 4 + col];
}
}
return handEyeMatrix;
}
CTVec3D FlangeAxisToEye(const CTHomogeneousMatrix& handEyeMatrix,
const CTVec3D& flangeLocalAxis)
{
// handEyeMatrix: T_flange_camera旋转部分 R 把 Eye 系向量映射到 Flange 系。
// 反向:把 Flange 系向量映射到 Eye 系 = R^T·v。
const CTRotationMatrix rotInv = handEyeMatrix.getRotation().transpose();
return NormalizeVector(rotInv.transform(flangeLocalAxis));
}
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<CTVec3D, 3>& axes,
double minPerpendicularity)
{
const CTVec3D xAxis = NormalizeVector(primary);
if (!IsValidVector(xAxis)) {
return false;
}
const CTVec3D refY = NormalizeVector(referenceY);
if (!IsValidVector(refY)) {
return false;
}
// Gram-SchmidtrefY 投影到与 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<CTVec3D, 3>& 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<CTVec3D, 3>& srcAxes,
const CTHomogeneousMatrix& transform,
std::array<CTVec3D, 3>& 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<CTVec3D, 3>& 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);
}
// 万向锁消歧:当 |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);
}
bool ComputeRobotPoseAngles(const std::array<CTVec3D, 3>& eyeAxes,
const CTHomogeneousMatrix& eyeInHandTransform,
const HandEyeExtrinsic& extrinsic,
CTEulerOrder eulerOrder,
double refRollDeg,
double refYawDeg,
PoseAngles& outAngles,
PoseDebugInfo* outDebugInfo)
{
std::array<CTVec3D, 3> eyeAxesWorking = eyeAxes;
if (outDebugInfo) {
const CTRotationMatrix rEyeBefore = BuildRotationMatrix(eyeAxesWorking);
RotationMatrixToConfiguredEulerDegrees(rEyeBefore, eulerOrder,
outDebugInfo->eyeEulerBefore.rollDeg,
outDebugInfo->eyeEulerBefore.pitchDeg,
outDebugInfo->eyeEulerBefore.yawDeg);
}
// 1) Eye 系内补偿
ApplyAxesRotation(eyeAxesWorking, extrinsic.rotX, extrinsic.rotY, extrinsic.rotZ);
if (outDebugInfo) {
outDebugInfo->eyeRotationAfter = BuildRotationMatrix(eyeAxesWorking);
RotationMatrixToConfiguredEulerDegrees(outDebugInfo->eyeRotationAfter, eulerOrder,
outDebugInfo->eyeEulerAfter.rollDeg,
outDebugInfo->eyeEulerAfter.pitchDeg,
outDebugInfo->eyeEulerAfter.yawDeg);
}
// 2) Eye → Robot 变换
std::array<CTVec3D, 3> robotAxes;
if (!TransformAxes(eyeAxesWorking, eyeInHandTransform, robotAxes)) {
return false;
}
// 2.5) outRot 补偿之前的 Robot 系姿态:这是 CloudView「姿态补偿」工具应当读到的「当前」。
// 把日志里打印的欧拉粘进"当前"栏,期望填机械臂期望的姿态,矩阵补偿 + XYZ 输出顺序,
// 反解出来的就是 outRotX/Y/Z。
if (outDebugInfo) {
const CTRotationMatrix rRobotBeforeOutRot = BuildRotationMatrix(robotAxes);
RotationMatrixToConfiguredEulerDegrees(rRobotBeforeOutRot, eulerOrder,
outDebugInfo->robotEulerBeforeOutRot.rollDeg,
outDebugInfo->robotEulerBeforeOutRot.pitchDeg,
outDebugInfo->robotEulerBeforeOutRot.yawDeg);
}
// 3) Robot 系内补偿
ApplyAxesRotation(robotAxes, extrinsic.outRotX, extrinsic.outRotY, extrinsic.outRotZ);
// 4) 提欧拉角 + 万向锁消歧
const CTRotationMatrix robotRotation = BuildRotationMatrix(robotAxes);
if (outDebugInfo) {
outDebugInfo->robotRotation = robotRotation;
}
RotationMatrixToConfiguredEulerDegrees(robotRotation, eulerOrder,
outAngles.rollDeg, outAngles.pitchDeg, outAngles.yawDeg);
ResolveGimbalAmbiguity(outAngles.rollDeg, outAngles.pitchDeg, outAngles.yawDeg,
refRollDeg, refYawDeg);
return true;
}
} // namespace PoseAxesBuilder