Utils/CloudView3D/Inc/PointCloudGLWidget.h

631 lines
22 KiB
C
Raw Normal View History

2026-07-13 18:56:01 +08:00
#ifndef POINT_CLOUD_GL_WIDGET_H
#define POINT_CLOUD_GL_WIDGET_H
#include <QOpenGLWidget>
#include <QOpenGLFunctions>
#include <QOpenGLBuffer>
2026-08-08 17:41:02 +08:00
#include <QOpenGLShaderProgram>
2026-07-13 18:56:01 +08:00
#include <QMatrix4x4>
#include <QVector3D>
#include <QQuaternion>
#include <QMouseEvent>
#include <QWheelEvent>
#include <QResizeEvent>
#include <deque>
2026-07-13 18:56:01 +08:00
#include <vector>
2026-08-08 17:41:02 +08:00
#include "CloudView3DTypes.h"
#include "VZNL_Types.h"
class QToolButton;
class QPushButton;
class QPinchGesture;
class QResizeEvent;
2026-07-13 18:56:01 +08:00
/**
* @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 线段数据
*/
2026-08-08 17:41:02 +08:00
struct LineSegment
{
float x1, y1, z1; // 起点
float x2, y2, z2; // 终点
2026-07-13 18:56:01 +08:00
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)
2026-08-08 17:41:02 +08:00
: 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 姿态点数据
*
2026-07-13 18:56:01 +08:00
* 坐标系约定(右手坐标系):
* - 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.
2026-07-13 18:56:01 +08:00
explicit PointCloudGLWidget(QWidget* parent = nullptr);
PointCloudGLWidget(QWidget* parent, bool showControlButtons);
2026-07-13 18:56:01 +08:00
~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 removeNewestPointCloud();
2026-08-08 17:41:02 +08:00
void clearPointClouds();
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();
2026-08-08 17:41:02 +08:00
void resetView();
void fitToContent();
void zoomIn();
2026-07-13 18:56:01 +08:00
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);
2026-07-13 18:56:01 +08:00
/**
* @brief 设置视角旋转角度(不改变缩放和平移)
* @param rotX X轴旋转角度(俯仰)
* @param rotY Y轴旋转角度(偏航)
* @param rotZ Z轴旋转角度(滚转)
*/
void setViewAngles(float rotX, float rotY, float rotZ = 0.0f);
QVector<SelectedPointInfo> 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<QVector3D> 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<std::vector<SVzNL3DPosition>>& scanLines) const;
/**
* @brief 添加线段
*/
void addLineSegments(const QVector<LineSegment>& segments);
/**
* @brief 清除所有线段
*/
void clearLineSegments();
2026-08-08 17:41:02 +08:00
/**
* @brief 设置基础图形线段
*/
void setBasicShapeSegments(const QVector<LineSegment>& segments);
/**
* @brief 追加基础图形线段
*/
void appendBasicShapeSegments(const QVector<LineSegment>& segments);
/**
* @brief 追加基础图形面片
*/
void appendBasicShapeSurfaces(const QVector<SurfaceQuad>& surfaces);
void appendBasicShapePoints(const QVector<BasicShapePoint>& points);
/**
* @brief 清除基础图形线段
*/
void clearBasicShapeSegments();
2026-07-13 18:56:01 +08:00
/**
* @brief 添加姿态点
*/
void addPosePoints(const QVector<PosePoint>& poses);
/**
* @brief 清除所有姿态点
*/
void clearPosePoints();
2026-08-08 17:41:02 +08:00
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);
2026-07-13 18:56:01 +08:00
protected:
bool event(QEvent* event) override;
2026-07-13 18:56:01 +08:00
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;
2026-08-08 17:41:02 +08:00
void resizeEvent(QResizeEvent* event) override;
2026-07-13 18:56:01 +08:00
private:
struct PointCloudData
{
std::vector<float> vertices;
std::vector<float> colors;
std::vector<int> lineIndices; // 每个点所属的线索引
std::vector<int> originalIndices; // 每个显示点在原始点云中的索引(用于计算原始index)
std::vector<int> pointInLineIndices; // 每个显示点在所属线中的索引(预计算,支持不等长线)
bool hasColor;
bool pseudoColor;
2026-07-13 18:56:01 +08:00
bool hasLineInfo; // 是否有线信息
QString name;
int colorIndex; // 点云颜色索引
int totalLines; // 总线数
int pointsPerLine; // 每线点数(网格化点云)
// 自定义点大小分组(RGBA 中 A > 1 的点按大小分组)
struct PointSizeGroup {
float pointSize; // 点大小
std::vector<size_t> indices; // 该大小的点在顶点数组中的索引(顶点索引 = indices[i] * 3)
};
std::vector<PointSizeGroup> customPointSizeGroups;
bool hasCustomPointSizes;
// VBO 缓冲区
QOpenGLBuffer vertexBuffer; // 顶点 VBO
QOpenGLBuffer colorBuffer; // 颜色 VBO
bool vboCreated; // VBO 是否已创建
PointCloudData()
: hasColor(false), pseudoColor(false), hasLineInfo(false), colorIndex(0)
2026-07-13 18:56:01 +08:00
, totalLines(0), pointsPerLine(0)
, hasCustomPointSizes(false)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(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)
2026-07-13 18:56:01 +08:00
, hasLineInfo(other.hasLineInfo)
, name(std::move(other.name))
, colorIndex(other.colorIndex)
, totalLines(other.totalLines)
, pointsPerLine(other.pointsPerLine)
, customPointSizeGroups(std::move(other.customPointSizeGroups))
, hasCustomPointSizes(other.hasCustomPointSizes)
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false)
{
// 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;
2026-07-13 18:56:01 +08:00
hasLineInfo = other.hasLineInfo;
name = std::move(other.name);
colorIndex = other.colorIndex;
totalLines = other.totalLines;
pointsPerLine = other.pointsPerLine;
customPointSizeGroups = std::move(other.customPointSizeGroups);
hasCustomPointSizes = other.hasCustomPointSizes;
// VBO 需要重建
other.vboCreated = false;
}
return *this;
}
// 禁止拷贝
PointCloudData(const PointCloudData&) = delete;
PointCloudData& operator=(const PointCloudData&) = delete;
};
void computeBoundingBox();
2026-08-08 17:41:02 +08:00
void updateProjection(const QMatrix4x4& modelView);
float fittedCameraDistance(float padding = 1.05f) const;
float minimumCameraDistance() const;
QVector3D colorForIndex(int colorIndex) const;
2026-07-13 18:56:01 +08:00
SelectedPointInfo pickPoint(int screenX, int screenY);
void drawSelectedPoints();
void drawMeasurementLine();
void drawAxis();
2026-08-08 17:41:02 +08:00
void drawSelectedLine(); // 绘制选中的线
void drawLineSegments(); // 绘制线段
void drawBasicShapeSurfaces(); // 绘制基础图形面片
void drawBasicShapeSegments(); // 绘制基础图形
void drawBasicShapePoints();
void drawLineSegmentList(const QVector<LineSegment>& segments);
void drawPosePoints(); // 绘制姿态点
2026-07-13 18:56:01 +08:00
void drawAxisLabels(); // 绘制坐标轴 XYZ 标注
QVector3D pseudoColorForZ(float z) const;
void rebuildPseudoColors(PointCloudData& data, size_t firstVertex);
void updatePseudoColors();
void createControlButtons();
void updateControlButtonGeometry();
2026-07-13 18:56:01 +08:00
void uploadToVBO(PointCloudData& data); // 上传数据到 VBO
void releaseVBO(PointCloudData& data); // 释放 VBO 资源
std::deque<PointCloudData> m_pointClouds;
2026-08-08 17:41:02 +08:00
// 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);
2026-07-13 18:56:01 +08:00
QMatrix4x4 m_projection;
QMatrix4x4 m_view;
QMatrix4x4 m_model;
2026-08-08 17:41:02 +08:00
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;
2026-07-13 18:56:01 +08:00
float m_distance;
float m_pinchStartDistance;
2026-07-13 18:56:01 +08:00
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;
2026-07-13 18:56:01 +08:00
LineSelectMode m_lineSelectMode;
EulerRotationOrder m_eulerRotationOrder;
bool m_measureDistanceEnabled;
QVector<SelectedPointInfo> 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; // 可用颜色数量
2026-08-08 17:41:02 +08:00
// 线段和姿态点数据
QVector<LineSegment> m_lineSegments;
QVector<LineSegment> m_basicShapeSegments;
QVector<SurfaceQuad> m_basicShapeSurfaces;
QVector<BasicShapePoint> m_basicShapePoints;
QVector<PosePoint> m_posePoints;
bool m_showControlButtons;
QWidget* m_controlPanel;
2026-08-08 17:41:02 +08:00
// 覆盖控件
QWidget* m_viewToolbar; // 左侧竖向视图按钮容器
QVector<QToolButton*> m_viewButtons;
QPushButton* m_viewAngleButton; // 左下角旋转角度显示按钮
2026-08-08 17:41:02 +08:00
QToolButton* m_zoomInButton;
QToolButton* m_zoomOutButton;
};
2026-07-13 18:56:01 +08:00
#endif // POINT_CLOUD_GL_WIDGET_H