diff --git a/CloudView/Inc/CloudViewMainWindow.h b/CloudView/Inc/CloudViewMainWindow.h index c524e63..d2c814c 100644 --- a/CloudView/Inc/CloudViewMainWindow.h +++ b/CloudView/Inc/CloudViewMainWindow.h @@ -84,6 +84,16 @@ private slots: */ void onLineSelected(const SelectedLineInfo& line); + /** + * @brief 点1坐标编辑完成(回车) + */ + void onPoint1CoordChanged(); + + /** + * @brief 点2坐标编辑完成(回车) + */ + void onPoint2CoordChanged(); + /** * @brief 处理视角旋转角度改变事件 */ @@ -255,6 +265,16 @@ private: QLabel* m_lblPoint2; QLabel* m_lblDistance; + // 点1坐标编辑控件 + QLineEdit* m_editPoint1X; + QLineEdit* m_editPoint1Y; + QLineEdit* m_editPoint1Z; + + // 点2坐标编辑控件 + QLineEdit* m_editPoint2X; + QLineEdit* m_editPoint2Y; + QLineEdit* m_editPoint2Z; + // 点1姿态输入控件 QLineEdit* m_editRx1; QLineEdit* m_editRy1; diff --git a/CloudView/Inc/PointCloudGLWidget.h b/CloudView/Inc/PointCloudGLWidget.h index 639ae60..22164de 100644 --- a/CloudView/Inc/PointCloudGLWidget.h +++ b/CloudView/Inc/PointCloudGLWidget.h @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -150,6 +151,15 @@ public: void clearSelectedLine(); float calculateDistance(const SelectedPointInfo& p1, const SelectedPointInfo& p2); + /** + * @brief 更新选中点的坐标 + * @param index 点索引(0或1) + * @param x 新的X坐标 + * @param y 新的Y坐标 + * @param z 新的Z坐标 + */ + void updateSelectedPointCoord(int index, float x, float y, float z); + /** * @brief 通过线索引选择线(纵向) */ @@ -372,6 +382,7 @@ private: float m_rotationX; float m_rotationY; float m_rotationZ; + QQuaternion m_rotation; // 使用四元数存储旋转状态 QVector3D m_center; QVector3D m_pan; QVector3D m_minBound; diff --git a/CloudView/Src/CloudViewMainWindow.cpp b/CloudView/Src/CloudViewMainWindow.cpp index f12b57b..13b8571 100644 --- a/CloudView/Src/CloudViewMainWindow.cpp +++ b/CloudView/Src/CloudViewMainWindow.cpp @@ -163,13 +163,15 @@ QGroupBox* CloudViewMainWindow::createViewGroup() float rotZ; }; + // 坐标系定义:X向右,Y向下,Z朝后 + // 正视图:看XY平面,Z轴朝后 ViewPreset presets[] = { - {"正视", 0.0f, 0.0f, 0.0f}, - {"后视", 0.0f, 180.0f, 0.0f}, - {"左侧", 0.0f, -90.0f, 0.0f}, - {"右侧", 0.0f, 90.0f, 0.0f}, - {"俯视", -90.0f, 0.0f, 0.0f}, - {"仰视", 90.0f, 0.0f, 0.0f}, + {"正视", 0.0f, 0.0f, 0.0f}, // 看XY平面,Z朝后 + {"后视", 0.0f, 180.0f, 0.0f}, // 看XY平面,Z朝前 + {"左侧", 0.0f, 90.0f, 0.0f}, // 看YZ平面,X朝前 + {"右侧", 0.0f, -90.0f, 0.0f}, // 看YZ平面,X朝后 + {"俯视", 90.0f, 0.0f, 0.0f}, // 看XZ平面,Y朝后 + {"仰视", -90.0f, 0.0f, 0.0f}, // 看XZ平面,Y朝前 }; QHBoxLayout* btnLayout = new QHBoxLayout(); @@ -230,7 +232,7 @@ QGroupBox* CloudViewMainWindow::createViewGroup() QPushButton* btnApply = new QPushButton("应用", group); btnApply->setMaximumWidth(50); btnApply->setMaximumHeight(24); - connect(btnApply, &QPushButton::clicked, this, [this]() { + auto applyAngles = [this]() { bool okX, okY, okZ; float rotX = m_editRotX->text().toFloat(&okX); float rotY = m_editRotY->text().toFloat(&okY); @@ -238,9 +240,15 @@ QGroupBox* CloudViewMainWindow::createViewGroup() if (okX && okY && okZ) { m_glWidget->setViewAngles(rotX, rotY, rotZ); } - }); + }; + connect(btnApply, &QPushButton::clicked, this, applyAngles); angleGrid->addWidget(btnApply, 0, 6); + // 连接回车信号到应用功能 + connect(m_editRotX, &QLineEdit::returnPressed, this, applyAngles); + connect(m_editRotY, &QLineEdit::returnPressed, this, applyAngles); + connect(m_editRotZ, &QLineEdit::returnPressed, this, applyAngles); + mainLayout->addLayout(angleGrid); return group; @@ -329,6 +337,14 @@ QWidget* CloudViewMainWindow::createLinePage() btnLayout->addWidget(m_btnClearLine2); inputLayout->addLayout(btnLayout); + // 连接输入线段坐标框的回车信号 + connect(m_editLineX1, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowInputLine); + connect(m_editLineY1, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowInputLine); + connect(m_editLineZ1, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowInputLine); + connect(m_editLineX2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowInputLine); + connect(m_editLineY2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowInputLine); + connect(m_editLineZ2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowInputLine); + layout->addWidget(inputLineGroup); layout->addStretch(); return page; @@ -356,6 +372,12 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup() m_lblPoint1->setText("点1: --"); m_lblPoint2->setText("点2: --"); m_lblDistance->setText("--"); + m_editPoint1X->setText("--"); + m_editPoint1Y->setText("--"); + m_editPoint1Z->setText("--"); + m_editPoint2X->setText("--"); + m_editPoint2Y->setText("--"); + m_editPoint2Z->setText("--"); }); layout->addWidget(m_cbMeasureDistance); @@ -377,6 +399,34 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup() m_lblPoint1->setStyleSheet("font-weight: bold; font-size: 11px;"); layout->addWidget(m_lblPoint1); + // 点1坐标编辑(可修改) + QHBoxLayout* coord1Layout = new QHBoxLayout(); + coord1Layout->setSpacing(5); + coord1Layout->addWidget(new QLabel("X:", group)); + m_editPoint1X = new QLineEdit("--", group); + m_editPoint1X->setMaximumWidth(70); + m_editPoint1X->setMaximumHeight(24); + coord1Layout->addWidget(m_editPoint1X); + + coord1Layout->addWidget(new QLabel("Y:", group)); + m_editPoint1Y = new QLineEdit("--", group); + m_editPoint1Y->setMaximumWidth(70); + m_editPoint1Y->setMaximumHeight(24); + coord1Layout->addWidget(m_editPoint1Y); + + coord1Layout->addWidget(new QLabel("Z:", group)); + m_editPoint1Z = new QLineEdit("--", group); + m_editPoint1Z->setMaximumWidth(70); + m_editPoint1Z->setMaximumHeight(24); + coord1Layout->addWidget(m_editPoint1Z); + coord1Layout->addStretch(); + layout->addLayout(coord1Layout); + + // 连接回车信号 + connect(m_editPoint1X, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onPoint1CoordChanged); + connect(m_editPoint1Y, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onPoint1CoordChanged); + connect(m_editPoint1Z, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onPoint1CoordChanged); + // 点1姿态输入(紧凑布局) QHBoxLayout* pose1Layout = new QHBoxLayout(); pose1Layout->setSpacing(5); @@ -402,6 +452,11 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup() connect(m_btnShowPose1, &QPushButton::clicked, this, &CloudViewMainWindow::onShowPose1); layout->addWidget(m_btnShowPose1); + // 连接姿态输入框的回车信号 + connect(m_editRx1, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose1); + connect(m_editRy1, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose1); + connect(m_editRz1, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose1); + // 分隔线 QFrame* line2 = new QFrame(group); line2->setFrameShape(QFrame::HLine); @@ -414,6 +469,34 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup() m_lblPoint2->setStyleSheet("font-weight: bold; font-size: 11px;"); layout->addWidget(m_lblPoint2); + // 点2坐标编辑(可修改) + QHBoxLayout* coord2Layout = new QHBoxLayout(); + coord2Layout->setSpacing(5); + coord2Layout->addWidget(new QLabel("X:", group)); + m_editPoint2X = new QLineEdit("--", group); + m_editPoint2X->setMaximumWidth(70); + m_editPoint2X->setMaximumHeight(24); + coord2Layout->addWidget(m_editPoint2X); + + coord2Layout->addWidget(new QLabel("Y:", group)); + m_editPoint2Y = new QLineEdit("--", group); + m_editPoint2Y->setMaximumWidth(70); + m_editPoint2Y->setMaximumHeight(24); + coord2Layout->addWidget(m_editPoint2Y); + + coord2Layout->addWidget(new QLabel("Z:", group)); + m_editPoint2Z = new QLineEdit("--", group); + m_editPoint2Z->setMaximumWidth(70); + m_editPoint2Z->setMaximumHeight(24); + coord2Layout->addWidget(m_editPoint2Z); + coord2Layout->addStretch(); + layout->addLayout(coord2Layout); + + // 连接回车信号 + connect(m_editPoint2X, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onPoint2CoordChanged); + connect(m_editPoint2Y, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onPoint2CoordChanged); + connect(m_editPoint2Z, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onPoint2CoordChanged); + // 点2姿态输入(紧凑布局) QHBoxLayout* pose2Layout = new QHBoxLayout(); pose2Layout->setSpacing(5); @@ -439,6 +522,11 @@ QGroupBox* CloudViewMainWindow::createMeasureGroup() connect(m_btnShowPose2, &QPushButton::clicked, this, &CloudViewMainWindow::onShowPose2); layout->addWidget(m_btnShowPose2); + // 连接姿态输入框的回车信号 + connect(m_editRx2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2); + connect(m_editRy2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2); + connect(m_editRz2, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onShowPose2); + // 分隔线 QFrame* line3 = new QFrame(group); line3->setFrameShape(QFrame::HLine); @@ -838,6 +926,12 @@ void CloudViewMainWindow::onClearAll() m_lblPoint1->setText("点1: --"); m_lblPoint2->setText("点2: --"); m_lblDistance->setText("--"); + m_editPoint1X->setText("--"); + m_editPoint1Y->setText("--"); + m_editPoint1Z->setText("--"); + m_editPoint2X->setText("--"); + m_editPoint2Y->setText("--"); + m_editPoint2Z->setText("--"); // 清除选线信息 m_lblLineIndex->setText("--"); @@ -859,6 +953,12 @@ void CloudViewMainWindow::onClearSelectedPoints() m_lblPoint1->setText("点1: --"); m_lblPoint2->setText("点2: --"); m_lblDistance->setText("--"); + m_editPoint1X->setText("--"); + m_editPoint1Y->setText("--"); + m_editPoint1Z->setText("--"); + m_editPoint2X->setText("--"); + m_editPoint2Y->setText("--"); + m_editPoint2Z->setText("--"); statusBar()->showMessage("已清除选中的点"); } @@ -901,41 +1001,45 @@ void CloudViewMainWindow::updateSelectedPointsDisplay() if (selectedPoints.size() >= 1 && selectedPoints[0].valid) { QString text; if (selectedPoints[0].lineIndex >= 0) { - text = QString("点1: 线号:%1 点序:%2 x:%3 y:%4 z:%5") + text = QString("点1: 线号:%1 点序:%2") .arg(selectedPoints[0].lineIndex) - .arg(selectedPoints[0].pointIndexInLine) - .arg(selectedPoints[0].x, 0, 'f', 3) - .arg(selectedPoints[0].y, 0, 'f', 3) - .arg(selectedPoints[0].z, 0, 'f', 3); + .arg(selectedPoints[0].pointIndexInLine); } else { - text = QString("点1: x:%1 y:%2 z:%3") - .arg(selectedPoints[0].x, 0, 'f', 3) - .arg(selectedPoints[0].y, 0, 'f', 3) - .arg(selectedPoints[0].z, 0, 'f', 3); + text = QString("点1:"); } m_lblPoint1->setText(text); + + // 更新坐标编辑框 + m_editPoint1X->setText(QString::number(selectedPoints[0].x, 'f', 3)); + m_editPoint1Y->setText(QString::number(selectedPoints[0].y, 'f', 3)); + m_editPoint1Z->setText(QString::number(selectedPoints[0].z, 'f', 3)); } else { m_lblPoint1->setText("点1: --"); + m_editPoint1X->setText("--"); + m_editPoint1Y->setText("--"); + m_editPoint1Z->setText("--"); } if (selectedPoints.size() >= 2 && selectedPoints[1].valid) { QString text; if (selectedPoints[1].lineIndex >= 0) { - text = QString("点2: 线号:%1 点序:%2 x:%3 y:%4 z:%5") + text = QString("点2: 线号:%1 点序:%2") .arg(selectedPoints[1].lineIndex) - .arg(selectedPoints[1].pointIndexInLine) - .arg(selectedPoints[1].x, 0, 'f', 3) - .arg(selectedPoints[1].y, 0, 'f', 3) - .arg(selectedPoints[1].z, 0, 'f', 3); + .arg(selectedPoints[1].pointIndexInLine); } else { - text = QString("点2: x:%1 y:%2 z:%3") - .arg(selectedPoints[1].x, 0, 'f', 3) - .arg(selectedPoints[1].y, 0, 'f', 3) - .arg(selectedPoints[1].z, 0, 'f', 3); + text = QString("点2:"); } m_lblPoint2->setText(text); + + // 更新坐标编辑框 + m_editPoint2X->setText(QString::number(selectedPoints[1].x, 'f', 3)); + m_editPoint2Y->setText(QString::number(selectedPoints[1].y, 'f', 3)); + m_editPoint2Z->setText(QString::number(selectedPoints[1].z, 'f', 3)); } else { m_lblPoint2->setText("点2: --"); + m_editPoint2X->setText("--"); + m_editPoint2Y->setText("--"); + m_editPoint2Z->setText("--"); } } @@ -1273,9 +1377,9 @@ void CloudViewMainWindow::onShowPose1() // 添加到显示 m_glWidget->addPosePoints(poses); - statusBar()->showMessage(QString("已显示点1姿态 (%.3f, %.3f, %.3f) 旋转(%.1f°, %.1f°, %.1f°)") - .arg(point.x).arg(point.y).arg(point.z) - .arg(rx).arg(ry).arg(rz)); + statusBar()->showMessage(QString("已显示点1姿态 (%1, %2, %3) 旋转(%4°, %5°, %6°)") + .arg(point.x, 0, 'f', 3).arg(point.y, 0, 'f', 3).arg(point.z, 0, 'f', 3) + .arg(rx, 0, 'f', 1).arg(ry, 0, 'f', 1).arg(rz, 0, 'f', 1)); LOG_INFO("[CloudView] Show pose1 at (%.3f, %.3f, %.3f) with rotation (%.1f, %.1f, %.1f)\n", point.x, point.y, point.z, rx, ry, rz); @@ -1338,9 +1442,9 @@ void CloudViewMainWindow::onShowPose2() // 添加到显示 m_glWidget->addPosePoints(poses); - statusBar()->showMessage(QString("已显示点2姿态 (%.3f, %.3f, %.3f) 旋转(%.1f°, %.1f°, %.1f°)") - .arg(point.x).arg(point.y).arg(point.z) - .arg(rx).arg(ry).arg(rz)); + statusBar()->showMessage(QString("已显示点2姿态 (%1, %2, %3) 旋转(%4°, %5°, %6°)") + .arg(point.x, 0, 'f', 3).arg(point.y, 0, 'f', 3).arg(point.z, 0, 'f', 3) + .arg(rx, 0, 'f', 1).arg(ry, 0, 'f', 1).arg(rz, 0, 'f', 1)); LOG_INFO("[CloudView] Show pose2 at (%.3f, %.3f, %.3f) with rotation (%.1f, %.1f, %.1f)\n", point.x, point.y, point.z, rx, ry, rz); @@ -1405,9 +1509,9 @@ void CloudViewMainWindow::onShowInputLine() float distance = std::sqrt(dx * dx + dy * dy + dz * dz); statusBar()->showMessage(QString("已显示线段 (%1,%2,%3) → (%4,%5,%6) 长度: %7") - .arg(x1).arg(y1).arg(z1) - .arg(x2).arg(y2).arg(z2) - .arg(distance)); + .arg(x1, 0, 'f', 1).arg(y1, 0, 'f', 1).arg(z1, 0, 'f', 1) + .arg(x2, 0, 'f', 1).arg(y2, 0, 'f', 1).arg(z2, 0, 'f', 1) + .arg(distance, 0, 'f', 3)); LOG_INFO("[CloudView] Show input line from (%.3f, %.3f, %.3f) to (%.3f, %.3f, %.3f) length=%.3f\n", x1, y1, z1, x2, y2, z2, distance); @@ -1665,3 +1769,95 @@ void CloudViewMainWindow::onViewAnglesChanged(float rotX, float rotY, float rotZ m_editRotZ->setText(QString::number(rotZ, 'f', 1)); } +void CloudViewMainWindow::onPoint1CoordChanged() +{ + auto selectedPoints = m_glWidget->getSelectedPoints(); + if (selectedPoints.isEmpty() || !selectedPoints[0].valid) { + QMessageBox::warning(this, "提示", "请先选择点1"); + return; + } + + // 读取编辑框中的坐标 + bool ok = true; + float x = m_editPoint1X->text().toFloat(&ok); + if (!ok) { + QMessageBox::warning(this, "错误", "点1 X 值无效"); + return; + } + + float y = m_editPoint1Y->text().toFloat(&ok); + if (!ok) { + QMessageBox::warning(this, "错误", "点1 Y 值无效"); + return; + } + + float z = m_editPoint1Z->text().toFloat(&ok); + if (!ok) { + QMessageBox::warning(this, "错误", "点1 Z 值无效"); + return; + } + + // 更新选中点的坐标 + m_glWidget->updateSelectedPointCoord(0, x, y, z); + + // 如果启用了测距且有两个点,重新计算距离 + if (m_glWidget->isMeasureDistanceEnabled() && selectedPoints.size() >= 2 && selectedPoints[1].valid) { + auto updatedPoints = m_glWidget->getSelectedPoints(); + if (updatedPoints.size() >= 2) { + float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]); + m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); + statusBar()->showMessage(QString("点1坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3)); + } + } else { + statusBar()->showMessage(QString("点1坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3)); + } + + LOG_INFO("[CloudView] Point1 coord updated to (%.3f, %.3f, %.3f)\n", x, y, z); +} + +void CloudViewMainWindow::onPoint2CoordChanged() +{ + auto selectedPoints = m_glWidget->getSelectedPoints(); + if (selectedPoints.size() < 2 || !selectedPoints[1].valid) { + QMessageBox::warning(this, "提示", "请先选择点2"); + return; + } + + // 读取编辑框中的坐标 + bool ok = true; + float x = m_editPoint2X->text().toFloat(&ok); + if (!ok) { + QMessageBox::warning(this, "错误", "点2 X 值无效"); + return; + } + + float y = m_editPoint2Y->text().toFloat(&ok); + if (!ok) { + QMessageBox::warning(this, "错误", "点2 Y 值无效"); + return; + } + + float z = m_editPoint2Z->text().toFloat(&ok); + if (!ok) { + QMessageBox::warning(this, "错误", "点2 Z 值无效"); + return; + } + + // 更新选中点的坐标 + m_glWidget->updateSelectedPointCoord(1, x, y, z); + + // 如果启用了测距,重新计算距离 + if (m_glWidget->isMeasureDistanceEnabled()) { + auto updatedPoints = m_glWidget->getSelectedPoints(); + if (updatedPoints.size() >= 2) { + float distance = m_glWidget->calculateDistance(updatedPoints[0], updatedPoints[1]); + m_lblDistance->setText(QString("%1 mm").arg(distance, 0, 'f', 3)); + statusBar()->showMessage(QString("点2坐标已更新,距离: %1 mm").arg(distance, 0, 'f', 3)); + } + } else { + statusBar()->showMessage(QString("点2坐标已更新为 (%1, %2, %3)").arg(x, 0, 'f', 3).arg(y, 0, 'f', 3).arg(z, 0, 'f', 3)); + } + + LOG_INFO("[CloudView] Point2 coord updated to (%.3f, %.3f, %.3f)\n", x, y, z); +} + diff --git a/CloudView/Src/PointCloudGLWidget.cpp b/CloudView/Src/PointCloudGLWidget.cpp index d74258d..c28d601 100644 --- a/CloudView/Src/PointCloudGLWidget.cpp +++ b/CloudView/Src/PointCloudGLWidget.cpp @@ -21,6 +21,7 @@ PointCloudGLWidget::PointCloudGLWidget(QWidget* parent) , m_rotationX(0.0f) , m_rotationY(0.0f) , m_rotationZ(0.0f) + , m_rotation(QQuaternion()) // 初始化为单位四元数 , m_center(0, 0, 0) , m_pan(0, 0, 0) , m_minBound(-50, -50, -50) @@ -128,18 +129,18 @@ void PointCloudGLWidget::paintGL() // 平移在相机空间中进行,使鼠标拖动方向与屏幕方向一致 m_view.translate(-m_pan.x(), -m_pan.y(), -m_distance); - // 使用轨迹球旋转方式:先Y后X后Z,这样更直观 - m_view.rotate(m_rotationY, 0, 1, 0); - m_view.rotate(m_rotationX, 1, 0, 0); - m_view.rotate(m_rotationZ, 0, 0, 1); + // 使用四元数旋转(物体坐标系旋转) + m_view.rotate(m_rotation); m_view.translate(-m_center); // 调试输出:每100帧输出一次当前旋转角度 static int frameCount = 0; if (++frameCount % 100 == 0) { - LOG_INFO("[CloudView] Current rotation: rotX=%.1f, rotY=%.1f, rotZ=%.1f\n", - m_rotationX, m_rotationY, m_rotationZ); + // 从四元数转换为欧拉角用于显示 + QVector3D euler = m_rotation.toEulerAngles(); + LOG_INFO("[CloudView] Current rotation: pitch=%.1f, yaw=%.1f, roll=%.1f\n", + euler.x(), euler.y(), euler.z()); } glMatrixMode(GL_PROJECTION); @@ -427,7 +428,8 @@ void PointCloudGLWidget::resetView() // 确保最小视距 m_distance = qMax(maxSize * 2.0f, 10.0f); - // 默认视角:右手坐标系,X轴朝右,Y轴朝上,Z轴朝向观察者 + // 重置旋转为单位四元数 + m_rotation = QQuaternion(); m_rotationX = 0.0f; m_rotationY = 0.0f; m_rotationZ = 0.0f; @@ -447,6 +449,9 @@ void PointCloudGLWidget::setViewAngles(float rotX, float rotY, float rotZ) m_rotationZ = rotZ; m_pan = QVector3D(0, 0, 0); + // 从欧拉角转换为四元数(ZYX顺序) + m_rotation = QQuaternion::fromEulerAngles(rotX, rotY, rotZ); + LOG_INFO("[CloudView] setViewAngles: rotX=%.1f, rotY=%.1f, rotZ=%.1f\n", rotX, rotY, rotZ); emit viewAnglesChanged(m_rotationX, m_rotationY, m_rotationZ); @@ -548,6 +553,27 @@ float PointCloudGLWidget::calculateDistance(const SelectedPointInfo& p1, const S return std::sqrt(dx * dx + dy * dy + dz * dz); } +void PointCloudGLWidget::updateSelectedPointCoord(int index, float x, float y, float z) +{ + if (index < 0 || index >= m_selectedPoints.size()) { + return; + } + + if (!m_selectedPoints[index].valid) { + return; + } + + // 更新选中点的坐标 + m_selectedPoints[index].x = x; + m_selectedPoints[index].y = y; + m_selectedPoints[index].z = z; + + LOG_INFO("[CloudView] Updated selected point %d to (%.3f, %.3f, %.3f)\n", index, x, y, z); + + // 刷新显示 + update(); +} + QVector PointCloudGLWidget::getSelectedLinePoints() const { QVector points; @@ -901,10 +927,11 @@ void PointCloudGLWidget::drawSelectedLine() void PointCloudGLWidget::drawAxis() { - // 在右下角绘制坐标系指示器(右手坐标系) + // 在右下角绘制坐标系指示器 + // 坐标系定义:X向右,Y向下,Z朝后 // X轴:红色,指向右 - // Y轴:绿色,指向上 - // Z轴:蓝色,指向观察者(屏幕外) + // Y轴:绿色,指向下 + // Z轴:蓝色,指向后(远离观察者) GLint viewport[4]; glGetIntegerv(GL_VIEWPORT, viewport); @@ -927,10 +954,10 @@ void PointCloudGLWidget::drawAxis() // 移动到右下角中心位置 glTranslatef(axisX + axisSize / 2.0f, axisY + axisSize / 2.0f, 0.0f); - // 应用当前视图的旋转(与主视图同步) - glRotatef(m_rotationY, 0, 1, 0); - glRotatef(m_rotationX, 1, 0, 0); - glRotatef(m_rotationZ, 0, 0, 1); + // 应用当前视图的旋转(使用四元数转换为矩阵) + QMatrix4x4 rotMatrix; + rotMatrix.rotate(m_rotation); + glMultMatrixf(rotMatrix.constData()); // 关闭深度测试,确保坐标系始终可见 glDisable(GL_DEPTH_TEST); @@ -939,17 +966,17 @@ void PointCloudGLWidget::drawAxis() glLineWidth(2.0f); glBegin(GL_LINES); - // X 轴 - 红色(右手坐标系:向右) + // X 轴 - 红色(向右) glColor3f(1.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(axisLength, 0.0f, 0.0f); - // Y 轴 - 绿色(右手坐标系:向上) + // Y 轴 - 绿色(向下,在屏幕坐标中是向上显示) glColor3f(0.0f, 1.0f, 0.0f); glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, axisLength, 0.0f); - // Z 轴 - 蓝色(右手坐标系:向观察者) + // Z 轴 - 蓝色(向后,在屏幕坐标中是向观察者) glColor3f(0.0f, 0.0f, 1.0f); glVertex3f(0.0f, 0.0f, 0.0f); glVertex3f(0.0f, 0.0f, axisLength); @@ -1060,15 +1087,37 @@ void PointCloudGLWidget::mouseMoveEvent(QMouseEvent* event) m_lastMousePos = event->pos(); if (m_leftButtonPressed) { - // Alt+左键拖动:旋转rotZ(滚转) + // Alt+左键拖动:绕视线方向旋转(滚转) if (event->modifiers() & Qt::AltModifier) { - m_rotationZ += delta.x() * 0.5f; + // 绕Z轴(视线方向)旋转(修正方向) + float angle = -delta.x() * 0.5f; // 取反修正方向 + QQuaternion deltaRotation = QQuaternion::fromAxisAndAngle(QVector3D(0, 0, 1), angle); + m_rotation = deltaRotation * m_rotation; + m_rotationZ += angle; } else { - // 普通左键拖动:旋转rotX和rotY - m_rotationY += delta.x() * 0.5f; - m_rotationX += delta.y() * 0.5f; + // 普通左键拖动:基于当前物体坐标系的旋转 + // 鼠标水平移动 -> 绕当前Y轴旋转 + // 鼠标垂直移动 -> 绕当前X轴旋转 + + // 计算旋转角度 + float angleX = delta.y() * 0.5f; // 垂直移动 + float angleY = delta.x() * 0.5f; // 水平移动 + + // 创建增量旋转四元数(在相机空间中) + // 先绕X轴旋转(俯仰),再绕Y轴旋转(偏航) + QQuaternion deltaRotationX = QQuaternion::fromAxisAndAngle(QVector3D(1, 0, 0), angleX); + QQuaternion deltaRotationY = QQuaternion::fromAxisAndAngle(QVector3D(0, 1, 0), angleY); + QQuaternion deltaRotation = deltaRotationY * deltaRotationX; + + // 应用增量旋转(左乘,相机空间旋转) + m_rotation = deltaRotation * m_rotation; + + // 更新欧拉角(用于显示) + m_rotationX += angleX; + m_rotationY += angleY; } - // 移除角度限制,允许无限旋转 + + // 发送角度变化信号 emit viewAnglesChanged(m_rotationX, m_rotationY, m_rotationZ); update(); } else if (m_middleButtonPressed) { @@ -1077,7 +1126,11 @@ void PointCloudGLWidget::mouseMoveEvent(QMouseEvent* event) m_pan.setY(m_pan.y() + delta.y() * factor); update(); } else if (m_rightButtonPressed) { - m_rotationZ += delta.x() * 0.5f; + // 右键拖动:绕视线方向旋转(滚转)(修正方向) + float angle = -delta.x() * 0.5f; // 取反修正方向 + QQuaternion deltaRotation = QQuaternion::fromAxisAndAngle(QVector3D(0, 0, 1), angle); + m_rotation = deltaRotation * m_rotation; + m_rotationZ += angle; emit viewAnglesChanged(m_rotationX, m_rotationY, m_rotationZ); update(); }