#ifndef POINT_CLOUD_GL_WIDGET_H #define POINT_CLOUD_GL_WIDGET_H #include #include #include #include #include #include #include #include #include #include #include #include #include #include "CloudView3DTypes.h" #include "VZNL_Types.h" class QToolButton; class QPushButton; class QPinchGesture; class QResizeEvent; class QTimer; /** * @brief 点云显示颜色枚举 */ enum class PointCloudColor { White, Red, Green, Blue, Yellow, Cyan, Magenta, Original }; /** * @brief 选中的点信息 */ struct SelectedPointInfo { bool valid; size_t index; float x, y, z; int cloudIndex; int lineIndex; // 所属线索引 int pointIndexInLine; // 点在线中的索引 SelectedPointInfo() : valid(false), index(0), x(0), y(0), z(0), cloudIndex(-1), lineIndex(-1), pointIndexInLine(-1) {} }; /** * @brief 选线模式 */ enum class LineSelectMode { Vertical, // 纵向选线(同一条扫描线) Horizontal // 横向选线(所有线的相同索引点) }; /** * @brief 欧拉角旋转顺序 */ enum class EulerRotationOrder { ZYX, // Yaw-Pitch-Roll(最常用) XYZ, // Roll-Pitch-Yaw ZXY, // Yaw-Roll-Pitch YXZ, // Pitch-Roll-Yaw XZY, // Roll-Yaw-Pitch YZX // Pitch-Yaw-Roll }; /** * @brief 选中的线信息 */ struct SelectedLineInfo { bool valid; int cloudIndex; // 点云索引 int lineIndex; // 线索引(纵向选线时使用) int pointIndex; // 点索引(横向选线时使用) int pointCount; // 该线上的点数 LineSelectMode mode; // 选线模式 SelectedLineInfo() : valid(false), cloudIndex(-1), lineIndex(-1), pointIndex(-1), pointCount(0), mode(LineSelectMode::Vertical) {} }; /** * @brief 线段数据 */ struct LineSegment { float x1, y1, z1; // 起点 float x2, y2, z2; // 终点 float r, g, b; // 颜色 (0-1) float lineWidth; // 线宽,0 表示使用默认值(来自 RGBA 中 A > 1 的值) LineSegment() : x1(0), y1(0), z1(0), x2(0), y2(0), z2(0), r(1), g(1), b(1), lineWidth(0) {} LineSegment(float _x1, float _y1, float _z1, float _x2, float _y2, float _z2, float _r = 1.0f, float _g = 1.0f, float _b = 1.0f, float _lw = 0) : x1(_x1), y1(_y1), z1(_z1), x2(_x2), y2(_y2), z2(_z2), r(_r), g(_g), b(_b), lineWidth(_lw) {} }; /** * @brief 面片数据 */ struct SurfaceQuad { QVector3D p1, p2, p3, p4; float r, g, b, a; SurfaceQuad() : p1(), p2(), p3(), p4(), r(0.0f), g(0.8f), b(1.0f), a(0.5f) {} SurfaceQuad(const QVector3D& _p1, const QVector3D& _p2, const QVector3D& _p3, const QVector3D& _p4, float _r = 0.0f, float _g = 0.8f, float _b = 1.0f, float _a = 0.5f) : p1(_p1), p2(_p2), p3(_p3), p4(_p4), r(_r), g(_g), b(_b), a(_a) {} }; struct BasicShapePoint { float x, y, z; float r, g, b; float pointSize; BasicShapePoint() : x(0), y(0), z(0), r(1), g(1), b(1), pointSize(8.0f) {} BasicShapePoint(float _x, float _y, float _z, float _r = 1.0f, float _g = 1.0f, float _b = 1.0f, float _size = 8.0f) : x(_x), y(_y), z(_z), r(_r), g(_g), b(_b), pointSize(_size) {} }; /** * @brief 姿态点数据 * * 坐标系约定(右手坐标系): * - X轴:红色,指向右 * - Y轴:绿色,指向上 * - Z轴:蓝色,指向观察者 * * 欧拉角旋转顺序:ZYX(Yaw-Pitch-Roll) * - rz: 绕Z轴旋转(偏航角,Yaw) * - ry: 绕Y轴旋转(俯仰角,Pitch) * - rx: 绕X轴旋转(滚转角,Roll) */ struct PosePoint { float x, y, z; // 位置 float rx, ry, rz; // 欧拉角(度) float scale; // 坐标系大小 PosePoint() : x(0), y(0), z(0), rx(0), ry(0), rz(0), scale(10.0f) {} PosePoint(float _x, float _y, float _z, float _rx, float _ry, float _rz, float _scale = 10.0f) : x(_x), y(_y), z(_z), rx(_rx), ry(_ry), rz(_rz), scale(_scale) {} }; /** * @brief 点云 OpenGL 渲染控件 */ class PointCloudGLWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: // Controls are enabled by default for standalone CloudView3D users. // Embedders such as CloudView can disable them and provide their own UI. explicit PointCloudGLWidget(QWidget* parent = nullptr); PointCloudGLWidget(QWidget* parent, bool showControlButtons); ~PointCloudGLWidget() override; void setControlButtonsVisible(bool visible); bool controlButtonsVisible() const { return m_showControlButtons; } void addPointCloud(const PointCloudXYZ& cloud, const QString& name = "", bool resetView = true); void addPointCloud(const PointCloudXYZRGB& cloud, const QString& name = "", bool resetView = true); /** 删除最早添加的一组点云,用于维护滚动显示窗口。 */ bool removeOldestPointCloud(bool refreshDisplay = true); /** 删除最近添加的一组点云,用于回滚未完成的增量加载。 */ bool removeNewestPointCloud(bool refreshDisplay = true); void clearPointClouds(); /** 配置流式点云刷新频率;CamTool 使用 30 Hz。 */ void setStreamRefreshRate(int refreshRateHz); int streamRefreshRate() const { return m_streamRefreshRate; } /** 配置流式点云最多保留的源扫描线数。 */ void setStreamLineLimit(int maxLines); int streamLineLimit() const { return m_streamLineLimit; } void startStreamRefresh(); void stopStreamRefresh(bool flushPending = true); /** 缓存一批本地线号从 0 开始的扫描线,下一次刷新时统一提交。 */ void queuePointCloudBatch(PointCloudXYZ cloud, int sourceLineCount, const QString& name = QString()); void flushPointCloudBatches(); void clearPointCloudBatches(); void setPointCloudColor(PointCloudColor color); void setPointSize(float size); /** Enable Z-based pseudo-coloring for point clouds added without RGB data. */ void setPseudoColorEnabled(bool enabled); bool isPseudoColorEnabled() const { return m_pseudoColorEnabled; } void setPseudoColorSettings(bool enabled, float period, const QVector3D& startColor, const QVector3D& endColor, bool defaultPalette); /** 设置伪彩循环周期(毫米);同一 Z 在同一周期内颜色相同。 */ void setPseudoColorPeriod(float period); float pseudoColorPeriod() const { return m_pseudoColorPeriod; } void setPseudoColorColorRange(const QVector3D& startColor, const QVector3D& endColor); QVector3D pseudoColorStartColor() const { return m_pseudoColorStartColor; } QVector3D pseudoColorEndColor() const { return m_pseudoColorEndColor; } /** 默认色带:颜色选择器色相条(S/V 最大)按固定周期循环。 */ void setPseudoColorDefaultPalette(bool enabled); bool isPseudoColorDefaultPalette() const { return m_pseudoColorDefaultPalette; } /** Restore the reference palette and all of its default parameters. */ void resetPseudoColorDefaults(); static float defaultPseudoColorPeriod(); static QVector3D defaultPseudoColorStartColor(); static QVector3D defaultPseudoColorEndColor(); void resetView(); void fitToContent(); void zoomIn(); void zoomOut(); void appendPointCloud(const PointCloudXYZRGB& cloud, const QString& name = ""); /** Append a chunk of an uncolored point cloud to the most recently added cloud. */ void appendPointCloud(const PointCloudXYZ& cloud, const QString& name = ""); /** 设置最近点云的完整源扫描线布局,保留已被过滤的尾部空线和空列。 */ void setNewestPointCloudLineLayout(int totalLines, int pointsPerLine); /** * @brief 设置视角旋转角度(不改变缩放和平移) * @param rotX X轴旋转角度(俯仰) * @param rotY Y轴旋转角度(偏航) * @param rotZ Z轴旋转角度(滚转) */ void setViewAngles(float rotX, float rotY, float rotZ = 0.0f); QVector getSelectedPoints() const { return m_selectedPoints; } SelectedLineInfo getSelectedLine() const { return m_selectedLine; } void clearSelectedPoints(); 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 设置选中点(如果不存在则创建) * @param index 点索引(0或1) * @param x X坐标 * @param y Y坐标 * @param z Z坐标 */ void setSelectedPointCoord(int index, float x, float y, float z); /** * @brief 通过线索引选择线(纵向) */ bool selectLineByIndex(int lineIndex); /** * @brief 通过点索引选择横向线(所有线的相同索引点) */ bool selectHorizontalLineByIndex(int pointIndex); /** * @brief 设置选线模式 */ void setLineSelectMode(LineSelectMode mode) { m_lineSelectMode = mode; } /** * @brief 设置欧拉角旋转顺序 */ void setEulerRotationOrder(EulerRotationOrder order) { m_eulerRotationOrder = order; } /** * @brief 获取欧拉角旋转顺序 */ EulerRotationOrder getEulerRotationOrder() const { return m_eulerRotationOrder; } /** * @brief 设置是否启用测距功能 */ void setMeasureDistanceEnabled(bool enabled) { m_measureDistanceEnabled = enabled; } /** * @brief 获取测距功能是否启用 */ bool isMeasureDistanceEnabled() const { return m_measureDistanceEnabled; } /** * @brief 获取选中线上的所有点坐标 * @return 点坐标列表,每个元素为 (x, y, z) */ QVector getSelectedLinePoints() const; /** * @brief 设置列表高亮点(与选点功能区分) * @param point 点坐标,如果为空则清除高亮 */ void setListHighlightPoint(const QVector3D& point); /** * @brief 清除列表高亮点 */ void clearListHighlightPoint(); /** * @brief 获取第一个点云的数据(用于旋转和保存) */ bool getFirstCloudData(PointCloudXYZ& cloud) const; /** * @brief 替换第一个点云(用于显示旋转后的数据) */ void replaceFirstCloud(const PointCloudXYZ& cloud, const QString& name); /** * @brief 获取点云数量 */ size_t getCloudCount() const { return m_pointClouds.size(); } /** * @brief 对所有点云应用 4x4 变换矩阵 * @param matrix 4x4 变换矩阵 */ void transformAllClouds(const QMatrix4x4& matrix); /** * @brief 对所有点云中 Z 值小于阈值的点应用旋转矩阵 * @param rotMatrix 旋转矩阵 * @param zThreshold Z 轴阈值 * @return 被旋转的点数 */ size_t rotateCloudsByZThreshold(const QMatrix4x4& rotMatrix, float zThreshold); /** * @brief 获取所有点云数据(按线分组),用于保存 * @param scanLines 输出:每条扫描线的点列表 * @return 总点数 */ size_t getAllCloudsByLines(std::vector>& scanLines) const; /** * @brief 添加线段 */ void addLineSegments(const QVector& segments); /** * @brief 清除所有线段 */ void clearLineSegments(); /** * @brief 设置基础图形线段 */ void setBasicShapeSegments(const QVector& segments); /** * @brief 追加基础图形线段 */ void appendBasicShapeSegments(const QVector& segments); /** * @brief 追加基础图形面片 */ void appendBasicShapeSurfaces(const QVector& surfaces); void appendBasicShapePoints(const QVector& points); /** * @brief 清除基础图形线段 */ void clearBasicShapeSegments(); /** * @brief 添加姿态点 */ void addPosePoints(const QVector& poses); /** * @brief 清除所有姿态点 */ void clearPosePoints(); signals: void pointSelected(const SelectedPointInfo& point); void twoPointsSelected(const SelectedPointInfo& p1, const SelectedPointInfo& p2, float distance); void measurementFinished(const SelectedPointInfo& p1, const SelectedPointInfo& p2, float distance, float dx, float dy, float dz); void lineSelected(const SelectedLineInfo& line); void viewAnglesChanged(float rotX, float rotY, float rotZ); /** 30 Hz 刷新边界;数据源应在该信号处理中批量填充缓存。 */ void streamRefreshRequested(); protected: bool event(QEvent* event) override; void initializeGL() override; void resizeGL(int w, int h) override; void paintGL() override; void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; void wheelEvent(QWheelEvent* event) override; void mouseDoubleClickEvent(QMouseEvent* event) override; void keyPressEvent(QKeyEvent* event) override; void resizeEvent(QResizeEvent* event) override; private: struct QueuedPointCloudBatch { PointCloudXYZ cloud; QString name; int sourceLineCount; }; struct PointCloudData { std::vector vertices; std::vector colors; std::vector lineIndices; // 每个点所属的线索引 std::vector originalIndices; // 每个显示点在原始点云中的索引(用于计算原始index) std::vector pointInLineIndices; // 每个显示点在所属线中的索引(预计算,支持不等长线) bool hasColor; bool pseudoColor; bool hasLineInfo; // 是否有线信息 QString name; int colorIndex; // 点云颜色索引 int totalLines; // 总线数 int pointsPerLine; // 每线点数(网格化点云) bool streamBatch; int sourceLineCount; // 自定义点大小分组(RGBA 中 A > 1 的点按大小分组) struct PointSizeGroup { float pointSize; // 点大小 std::vector indices; // 该大小的点在顶点数组中的索引(顶点索引 = indices[i] * 3) }; std::vector customPointSizeGroups; bool hasCustomPointSizes; // VBO 缓冲区 QOpenGLBuffer vertexBuffer; // 顶点 VBO QOpenGLBuffer colorBuffer; // 颜色 VBO bool vboCreated; // VBO 是否已创建 QVector3D minBound; QVector3D maxBound; bool hasBounds; PointCloudData() : hasColor(false), pseudoColor(false), hasLineInfo(false), colorIndex(0) , totalLines(0), pointsPerLine(0) , streamBatch(false), sourceLineCount(0) , hasCustomPointSizes(false) , vertexBuffer(QOpenGLBuffer::VertexBuffer) , colorBuffer(QOpenGLBuffer::VertexBuffer) , vboCreated(false) , hasBounds(false) {} // 移动构造函数(QOpenGLBuffer 不支持拷贝) PointCloudData(PointCloudData&& other) noexcept : vertices(std::move(other.vertices)) , colors(std::move(other.colors)) , lineIndices(std::move(other.lineIndices)) , originalIndices(std::move(other.originalIndices)) , pointInLineIndices(std::move(other.pointInLineIndices)) , hasColor(other.hasColor) , pseudoColor(other.pseudoColor) , hasLineInfo(other.hasLineInfo) , name(std::move(other.name)) , colorIndex(other.colorIndex) , totalLines(other.totalLines) , pointsPerLine(other.pointsPerLine) , streamBatch(other.streamBatch) , sourceLineCount(other.sourceLineCount) , customPointSizeGroups(std::move(other.customPointSizeGroups)) , hasCustomPointSizes(other.hasCustomPointSizes) , vertexBuffer(QOpenGLBuffer::VertexBuffer) , colorBuffer(QOpenGLBuffer::VertexBuffer) , vboCreated(false) , minBound(other.minBound) , maxBound(other.maxBound) , hasBounds(other.hasBounds) { // VBO 需要在 GL 上下文中重建,标记为未创建 other.vboCreated = false; } PointCloudData& operator=(PointCloudData&& other) noexcept { if (this != &other) { // 释放自身 VBO if (vboCreated) { vertexBuffer.destroy(); colorBuffer.destroy(); vboCreated = false; } vertices = std::move(other.vertices); colors = std::move(other.colors); lineIndices = std::move(other.lineIndices); originalIndices = std::move(other.originalIndices); pointInLineIndices = std::move(other.pointInLineIndices); hasColor = other.hasColor; pseudoColor = other.pseudoColor; hasLineInfo = other.hasLineInfo; name = std::move(other.name); colorIndex = other.colorIndex; totalLines = other.totalLines; pointsPerLine = other.pointsPerLine; streamBatch = other.streamBatch; sourceLineCount = other.sourceLineCount; customPointSizeGroups = std::move(other.customPointSizeGroups); hasCustomPointSizes = other.hasCustomPointSizes; minBound = other.minBound; maxBound = other.maxBound; hasBounds = other.hasBounds; // VBO 需要重建 other.vboCreated = false; } return *this; } // 禁止拷贝 PointCloudData(const PointCloudData&) = delete; PointCloudData& operator=(const PointCloudData&) = delete; }; void computeBoundingBox(); void updateProjection(const QMatrix4x4& modelView); float fittedCameraDistance(float padding = 1.05f) const; float minimumCameraDistance() const; /** 按当前缩放把点大小从逻辑像素换到实际绘制像素,避免放大后点散成看不见的碎点。 */ float effectivePointSize() const; QVector3D colorForIndex(int colorIndex) const; SelectedPointInfo pickPoint(int screenX, int screenY); void drawSelectedPoints(); void drawMeasurementLine(); void drawAxis(); void drawSelectedLine(); // 绘制选中的线 void drawLineSegments(); // 绘制线段 void drawBasicShapeSurfaces(); // 绘制基础图形面片 void drawBasicShapeSegments(); // 绘制基础图形 void drawBasicShapePoints(); void drawLineSegmentList(const QVector& segments); void drawPosePoints(); // 绘制姿态点 void drawAxisLabels(); // 绘制坐标轴 XYZ 标注 void drawSelectionInfoOverlay(); // 右上角显示选点和测量结果 QVector3D pseudoColorForZ(float z) const; void rebuildPseudoColors(PointCloudData& data, size_t firstVertex); void updatePseudoColors(); void createControlButtons(); void updateControlButtonGeometry(); void uploadToVBO(PointCloudData& data); // 上传数据到 VBO void releaseVBO(PointCloudData& data); // 释放 VBO 资源 void expandCloudBounds(PointCloudData& data, float x, float y, float z); void addPointCloudInternal(const PointCloudXYZ& cloud, const QString& name, bool resetViewAfterAdd, bool refreshDisplay); void trimQueuedPointCloudBatches(); void trimDisplayedStreamBatches(bool refreshDisplay); bool removeOldestStreamPointCloud(bool refreshDisplay); void discardLeadingLines(QueuedPointCloudBatch& batch, int lineCount); void discardLeadingStreamLines(PointCloudData& data, int lineCount); void recomputeCloudBounds(PointCloudData& data); std::deque m_pointClouds; // GLES2 shader 渲染 bool ensureShader(); void drawCloud(PointCloudData& data, float pointSize); void drawPrimitives(const QMatrix4x4& mvp, const float* positions, int vertexCount, GLenum mode, const float* colors, const QVector3D& uniformColor, float pointSize, float alpha, bool depthTest); void resetRenderState(); // 覆盖控件:左侧竖向视图按钮 / 左下角旋转角度 / 右侧缩放 void createOverlayControls(); void repositionOverlayControls(); void showViewAngleDialog(); void updateViewAngleDisplay(); bool handlePinchGesture(QPinchGesture* gesture); QMatrix4x4 m_projection; QMatrix4x4 m_view; QMatrix4x4 m_model; QMatrix4x4 m_mvp; // 当前帧的模型视图投影矩阵(点云主渲染用) // GLES2 shader 渲染资源 QOpenGLShaderProgram* m_shader; QOpenGLBuffer m_dynamicVbo; // 动态叠加图元(选中点/线/轴/姿态等)的临时 VBO bool m_shaderReady; GLint m_mvpLocation; GLint m_positionLocation; GLint m_colorLocation; GLint m_pointSizeLocation; GLint m_alphaLocation; GLint m_useVertexColorLocation; GLint m_uniformColorLocation; float m_distance; float m_pinchStartDistance; float m_rotationX; float m_rotationY; float m_rotationZ; QQuaternion m_rotation; // 使用四元数存储旋转状态 QVector3D m_center; QVector3D m_pan; QVector3D m_minBound; QVector3D m_maxBound; QPoint m_lastMousePos; bool m_leftButtonPressed; bool m_rightButtonPressed; bool m_middleButtonPressed; PointCloudColor m_currentColor; float m_pointSize; bool m_pseudoColorEnabled; float m_pseudoColorPeriod; QVector3D m_pseudoColorStartColor; QVector3D m_pseudoColorEndColor; bool m_pseudoColorDefaultPalette; LineSelectMode m_lineSelectMode; EulerRotationOrder m_eulerRotationOrder; bool m_measureDistanceEnabled; QVector m_selectedPoints; SelectedLineInfo m_selectedLine; static const int MAX_SELECTED_POINTS = 2; // 列表高亮点(与选点功能区分) bool m_hasListHighlightPoint; QVector3D m_listHighlightPoint; int m_colorIndex; // 颜色轮换索引 static const int COLOR_COUNT = 7; // 可用颜色数量 // 线段和姿态点数据 QVector m_lineSegments; QVector m_basicShapeSegments; QVector m_basicShapeSurfaces; QVector m_basicShapePoints; QVector m_posePoints; bool m_showControlButtons; QWidget* m_controlPanel; // 覆盖控件 QWidget* m_viewToolbar; // 左侧竖向视图按钮容器 QVector m_viewButtons; QPushButton* m_viewAngleButton; // 左下角旋转角度显示按钮 QToolButton* m_zoomInButton; QToolButton* m_zoomOutButton; // 高速输入先进入 CPU 有界缓存,再由 GUI 线程按固定频率批量提交 OpenGL。 QTimer* m_streamRefreshTimer; std::deque m_queuedPointCloudBatches; int m_queuedPointCloudLineCount; int m_streamDisplayedLineCount; int m_streamRefreshRate; int m_streamLineLimit; }; #endif // POINT_CLOUD_GL_WIDGET_H