GrabBag/Tools/CloudView/Src/PointCloudGLWidget.cpp

902 lines
28 KiB
C++
Raw Normal View History

2026-01-16 01:04:43 +08:00
#include "PointCloudGLWidget.h"
#include <QDebug>
#include <QtMath>
#include <cfloat>
#include "VrLog.h"
// OpenGL/GLU 头文件
#ifdef _WIN32
#include <windows.h>
#include <GL/gl.h>
#include <GL/glu.h>
#else
#include <GL/gl.h>
#include <GL/glu.h>
#endif
PointCloudGLWidget::PointCloudGLWidget(QWidget* parent)
: QOpenGLWidget(parent)
, m_distance(100.0f)
, m_rotationX(0.0f)
, m_rotationY(0.0f)
, m_center(0, 0, 0)
, m_pan(0, 0, 0)
, m_minBound(-50, -50, -50)
, m_maxBound(50, 50, 50)
, m_leftButtonPressed(false)
, m_rightButtonPressed(false)
, m_middleButtonPressed(false)
, m_currentColor(PointCloudColor::White)
, m_pointSize(1.0f)
, m_lineSelectMode(LineSelectMode::Vertical)
, m_colorIndex(0)
{
setFocusPolicy(Qt::StrongFocus);
setMouseTracking(true);
// 确保 m_model 是单位矩阵
m_model.setToIdentity();
}
PointCloudGLWidget::~PointCloudGLWidget()
{
}
void PointCloudGLWidget::initializeGL()
{
initializeOpenGLFunctions();
LOG_INFO("[CloudView] OpenGL initialized, version: %s\n", reinterpret_cast<const char*>(glGetString(GL_VERSION)));
glClearColor(0.15f, 0.15f, 0.15f, 1.0f);
glEnable(GL_DEPTH_TEST);
glEnable(GL_POINT_SMOOTH);
glHint(GL_POINT_SMOOTH_HINT, GL_NICEST);
}
void PointCloudGLWidget::resizeGL(int w, int h)
{
glViewport(0, 0, w, h);
m_projection.setToIdentity();
float aspect = static_cast<float>(w) / static_cast<float>(h > 0 ? h : 1);
m_projection.perspective(45.0f, aspect, 0.1f, 10000.0f);
}
void PointCloudGLWidget::paintGL()
{
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
m_view.setToIdentity();
// 平移在相机空间中进行,使鼠标拖动方向与屏幕方向一致
m_view.translate(-m_pan.x(), -m_pan.y(), -m_distance);
m_view.rotate(m_rotationX, 1, 0, 0);
m_view.rotate(m_rotationY, 0, 1, 0);
m_view.translate(-m_center);
glMatrixMode(GL_PROJECTION);
glLoadMatrixf(m_projection.constData());
glMatrixMode(GL_MODELVIEW);
QMatrix4x4 modelView = m_view * m_model;
glLoadMatrixf(modelView.constData());
// 确保颜色数组禁用
glDisableClientState(GL_COLOR_ARRAY);
glPointSize(m_pointSize);
for (size_t cloudIdx = 0; cloudIdx < m_pointClouds.size(); ++cloudIdx) {
const auto& cloudData = m_pointClouds[cloudIdx];
if (cloudData.vertices.empty()) {
continue;
}
glEnableClientState(GL_VERTEX_ARRAY);
glVertexPointer(3, GL_FLOAT, 0, cloudData.vertices.data());
// 如果有原始颜色且选择显示原始颜色
if (cloudData.hasColor && !cloudData.colors.empty()) {
glEnableClientState(GL_COLOR_ARRAY);
glColorPointer(3, GL_FLOAT, 0, cloudData.colors.data());
} else {
// 使用点云自己的颜色索引
glDisableClientState(GL_COLOR_ARRAY);
setColorByIndex(cloudData.colorIndex);
}
glDrawArrays(GL_POINTS, 0, static_cast<GLsizei>(cloudData.vertices.size() / 3));
glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY);
}
drawSelectedPoints();
drawMeasurementLine();
drawSelectedLine();
// 最后绘制坐标系指示器(覆盖在所有内容之上)
drawAxis();
}
void PointCloudGLWidget::addPointCloud(const PointCloudXYZ& cloud, const QString& name)
{
LOG_INFO("[CloudView] addPointCloud called, cloud size: %zu\n", cloud.size());
if (cloud.empty()) {
LOG_WARN("[CloudView] Cloud is empty, returning\n");
return;
}
PointCloudData data;
data.name = name;
data.hasColor = false;
data.hasLineInfo = !cloud.lineIndices.empty();
data.colorIndex = m_colorIndex; // 分配颜色索引
m_colorIndex = (m_colorIndex + 1) % COLOR_COUNT; // 轮换到下一个颜色
data.vertices.reserve(cloud.size() * 3);
data.totalLines = 0;
data.pointsPerLine = 0;
const float EPSILON = 1e-6f;
for (size_t i = 0; i < cloud.points.size(); ++i) {
const auto& pt = cloud.points[i];
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
continue;
}
// 显示时过滤 (0,0,0) 点
if (std::fabs(pt.x) < EPSILON && std::fabs(pt.y) < EPSILON && std::fabs(pt.z) < EPSILON) {
continue;
}
data.vertices.push_back(pt.x);
data.vertices.push_back(pt.y);
data.vertices.push_back(pt.z);
// 保存原始索引
data.originalIndices.push_back(static_cast<int>(i));
// 保存线索引
if (data.hasLineInfo && i < cloud.lineIndices.size()) {
int lineIdx = cloud.lineIndices[i];
data.lineIndices.push_back(lineIdx);
if (lineIdx + 1 > data.totalLines) {
data.totalLines = lineIdx + 1;
}
}
}
// 计算每线点数(假设网格化点云)
if (data.totalLines > 0) {
data.pointsPerLine = static_cast<int>(cloud.size()) / data.totalLines;
}
LOG_INFO("[CloudView] Valid vertices count: %zu, colorIndex: %d, totalLines: %d, pointsPerLine: %d\n",
data.vertices.size() / 3, data.colorIndex, data.totalLines, data.pointsPerLine);
m_pointClouds.push_back(std::move(data));
computeBoundingBox();
LOG_INFO("[CloudView] BoundingBox min: (%.3f, %.3f, %.3f) max: (%.3f, %.3f, %.3f)\n",
m_minBound.x(), m_minBound.y(), m_minBound.z(),
m_maxBound.x(), m_maxBound.y(), m_maxBound.z());
LOG_INFO("[CloudView] Center: (%.3f, %.3f, %.3f) Distance: %.3f\n",
m_center.x(), m_center.y(), m_center.z(), m_distance);
resetView();
update();
}
void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QString& name)
{
if (cloud.empty()) {
return;
}
PointCloudData data;
data.name = name;
data.hasColor = true;
data.hasLineInfo = !cloud.lineIndices.empty();
data.colorIndex = m_colorIndex; // 即使有颜色也分配索引(备用)
m_colorIndex = (m_colorIndex + 1) % COLOR_COUNT;
data.vertices.reserve(cloud.size() * 3);
data.colors.reserve(cloud.size() * 3);
data.totalLines = 0;
data.pointsPerLine = 0;
for (size_t i = 0; i < cloud.points.size(); ++i) {
const auto& pt = cloud.points[i];
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
continue;
}
data.vertices.push_back(pt.x);
data.vertices.push_back(pt.y);
data.vertices.push_back(pt.z);
data.colors.push_back(pt.r / 255.0f);
data.colors.push_back(pt.g / 255.0f);
data.colors.push_back(pt.b / 255.0f);
// 保存原始索引
data.originalIndices.push_back(static_cast<int>(i));
// 保存线索引
if (data.hasLineInfo && i < cloud.lineIndices.size()) {
int lineIdx = cloud.lineIndices[i];
data.lineIndices.push_back(lineIdx);
if (lineIdx + 1 > data.totalLines) {
data.totalLines = lineIdx + 1;
}
}
}
// 计算每线点数
if (data.totalLines > 0) {
data.pointsPerLine = static_cast<int>(cloud.size()) / data.totalLines;
}
m_pointClouds.push_back(std::move(data));
computeBoundingBox();
resetView();
update();
}
void PointCloudGLWidget::clearPointClouds()
{
m_pointClouds.clear();
m_selectedPoints.clear();
m_selectedLine = SelectedLineInfo();
m_minBound = QVector3D(-50, -50, -50);
m_maxBound = QVector3D(50, 50, 50);
m_center = QVector3D(0, 0, 0);
m_colorIndex = 0; // 重置颜色索引
resetView();
update();
}
void PointCloudGLWidget::setPointCloudColor(PointCloudColor color)
{
m_currentColor = color;
update();
}
void PointCloudGLWidget::setPointSize(float size)
{
m_pointSize = qBound(1.0f, size, 10.0f);
update();
}
void PointCloudGLWidget::resetView()
{
QVector3D size = m_maxBound - m_minBound;
float maxSize = qMax(qMax(size.x(), size.y()), size.z());
// 确保最小视距
m_distance = qMax(maxSize * 2.0f, 10.0f);
m_rotationX = 0.0f; // X朝右Y朝上Z面向使用者
m_rotationY = 0.0f;
m_pan = QVector3D(0, 0, 0);
LOG_INFO("[CloudView] resetView: size=(%.3f, %.3f, %.3f) maxSize=%.3f distance=%.3f\n",
size.x(), size.y(), size.z(), maxSize, m_distance);
update();
}
void PointCloudGLWidget::clearSelectedPoints()
{
m_selectedPoints.clear();
update();
}
void PointCloudGLWidget::clearSelectedLine()
{
m_selectedLine = SelectedLineInfo();
update();
}
bool PointCloudGLWidget::selectLineByIndex(int lineIndex)
{
if (m_pointClouds.empty()) {
return false;
}
const auto& cloudData = m_pointClouds[0];
if (!cloudData.hasLineInfo) {
return false;
}
// 检查线索引是否有效
if (lineIndex < 0 || lineIndex >= cloudData.totalLines) {
return false;
}
// 计算该线上的点数
int pointCount = 0;
for (int idx : cloudData.lineIndices) {
if (idx == lineIndex) {
pointCount++;
}
}
if (pointCount == 0) {
return false;
}
// 设置选中的线
m_selectedLine.valid = true;
m_selectedLine.cloudIndex = 0;
m_selectedLine.lineIndex = lineIndex;
m_selectedLine.pointIndex = -1;
m_selectedLine.pointCount = pointCount;
m_selectedLine.mode = LineSelectMode::Vertical;
emit lineSelected(m_selectedLine);
update();
return true;
}
bool PointCloudGLWidget::selectHorizontalLineByIndex(int pointIndex)
{
if (m_pointClouds.empty()) {
return false;
}
const auto& cloudData = m_pointClouds[0];
if (!cloudData.hasLineInfo || cloudData.totalLines <= 0 || cloudData.pointsPerLine <= 0) {
return false;
}
// 检查点索引是否有效(使用原始点云的每线点数)
if (pointIndex < 0 || pointIndex >= cloudData.pointsPerLine) {
return false;
}
// 设置选中的横向线
m_selectedLine.valid = true;
m_selectedLine.cloudIndex = 0;
m_selectedLine.lineIndex = -1;
m_selectedLine.pointIndex = pointIndex;
m_selectedLine.pointCount = cloudData.totalLines; // 横向线的点数等于总线数
m_selectedLine.mode = LineSelectMode::Horizontal;
emit lineSelected(m_selectedLine);
update();
return true;
}
float PointCloudGLWidget::calculateDistance(const SelectedPointInfo& p1, const SelectedPointInfo& p2)
{
if (!p1.valid || !p2.valid) {
return 0.0f;
}
float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
float dz = p2.z - p1.z;
return std::sqrt(dx * dx + dy * dy + dz * dz);
}
void PointCloudGLWidget::computeBoundingBox()
{
if (m_pointClouds.empty()) {
m_minBound = QVector3D(-50, -50, -50);
m_maxBound = QVector3D(50, 50, 50);
m_center = QVector3D(0, 0, 0);
return;
}
float minX = FLT_MAX, minY = FLT_MAX, minZ = FLT_MAX;
float maxX = -FLT_MAX, maxY = -FLT_MAX, maxZ = -FLT_MAX;
for (const auto& cloudData : m_pointClouds) {
for (size_t i = 0; i < cloudData.vertices.size(); i += 3) {
float x = cloudData.vertices[i];
float y = cloudData.vertices[i + 1];
float z = cloudData.vertices[i + 2];
minX = qMin(minX, x);
minY = qMin(minY, y);
minZ = qMin(minZ, z);
maxX = qMax(maxX, x);
maxY = qMax(maxY, y);
maxZ = qMax(maxZ, z);
}
}
m_minBound = QVector3D(minX, minY, minZ);
m_maxBound = QVector3D(maxX, maxY, maxZ);
m_center = (m_minBound + m_maxBound) / 2.0f;
}
void PointCloudGLWidget::setCurrentColor(PointCloudColor color)
{
switch (color) {
case PointCloudColor::White: glColor3f(1.0f, 1.0f, 1.0f); break;
case PointCloudColor::Red: glColor3f(1.0f, 0.0f, 0.0f); break;
case PointCloudColor::Green: glColor3f(0.0f, 1.0f, 0.0f); break;
case PointCloudColor::Blue: glColor3f(0.0f, 0.0f, 1.0f); break;
case PointCloudColor::Yellow: glColor3f(1.0f, 1.0f, 0.0f); break;
case PointCloudColor::Cyan: glColor3f(0.0f, 1.0f, 1.0f); break;
case PointCloudColor::Magenta: glColor3f(1.0f, 0.0f, 1.0f); break;
default: glColor3f(1.0f, 1.0f, 1.0f); break;
}
}
void PointCloudGLWidget::setColorByIndex(int colorIndex)
{
// 颜色表:浅灰、浅绿、浅蓝、黄、青、品红、白
static const float colorTable[7][3] = {
{0.75f, 0.75f, 0.75f}, // 浅灰色
{0.3f, 1.0f, 0.3f}, // 浅绿
{0.3f, 0.3f, 1.0f}, // 浅蓝
{1.0f, 1.0f, 0.3f}, // 黄
{0.3f, 1.0f, 1.0f}, // 青
{1.0f, 0.3f, 1.0f}, // 品红
{1.0f, 1.0f, 1.0f}, // 白
};
int idx = colorIndex % COLOR_COUNT;
glColor3f(colorTable[idx][0], colorTable[idx][1], colorTable[idx][2]);
}
SelectedPointInfo PointCloudGLWidget::pickPoint(int screenX, int screenY)
{
SelectedPointInfo result;
if (m_pointClouds.empty()) {
return result;
}
// 获取视口和矩阵
GLint viewport[4];
GLdouble modelMatrix[16], projMatrix[16];
glGetIntegerv(GL_VIEWPORT, viewport);
glGetDoublev(GL_MODELVIEW_MATRIX, modelMatrix);
glGetDoublev(GL_PROJECTION_MATRIX, projMatrix);
// 转换屏幕 Y 坐标OpenGL Y 轴向上)
int glScreenY = viewport[3] - screenY;
float minScreenDist = FLT_MAX;
size_t bestIndex = 0;
int bestCloudIndex = -1;
int bestLineIndex = -1;
float bestX = 0, bestY = 0, bestZ = 0;
// 遍历所有点,计算屏幕投影距离
for (size_t cloudIdx = 0; cloudIdx < m_pointClouds.size(); ++cloudIdx) {
const auto& cloudData = m_pointClouds[cloudIdx];
for (size_t i = 0; i < cloudData.vertices.size(); i += 3) {
float wx = cloudData.vertices[i];
float wy = cloudData.vertices[i + 1];
float wz = cloudData.vertices[i + 2];
// 将世界坐标投影到屏幕
GLdouble sx, sy, sz;
gluProject(wx, wy, wz, modelMatrix, projMatrix, viewport, &sx, &sy, &sz);
// 计算屏幕距离
float dx = static_cast<float>(sx) - screenX;
float dy = static_cast<float>(sy) - glScreenY;
float screenDist = dx * dx + dy * dy;
if (screenDist < minScreenDist) {
minScreenDist = screenDist;
bestIndex = i / 3;
bestCloudIndex = static_cast<int>(cloudIdx);
bestX = wx;
bestY = wy;
bestZ = wz;
// 获取线索引
if (cloudData.hasLineInfo && bestIndex < cloudData.lineIndices.size()) {
bestLineIndex = cloudData.lineIndices[bestIndex];
} else {
bestLineIndex = -1;
}
}
}
}
// 屏幕距离阈值像素20像素内认为选中
float threshold = 20.0f * 20.0f;
if (minScreenDist < threshold) {
result.valid = true;
result.index = bestIndex;
result.cloudIndex = bestCloudIndex;
result.lineIndex = bestLineIndex;
result.x = bestX;
result.y = bestY;
result.z = bestZ;
// 计算点在线中的原始索引
if (bestLineIndex >= 0 && bestCloudIndex >= 0) {
const auto& cloudData = m_pointClouds[bestCloudIndex];
// 使用原始索引计算:原始索引 % 每线点数
if (bestIndex < cloudData.originalIndices.size() && cloudData.pointsPerLine > 0) {
int originalIdx = cloudData.originalIndices[bestIndex];
result.pointIndexInLine = originalIdx % cloudData.pointsPerLine;
}
}
LOG_INFO("[CloudView] Point selected: (%.3f, %.3f, %.3f) lineIndex=%d pointIndexInLine=%d screenDist=%.1f\n",
bestX, bestY, bestZ, bestLineIndex, result.pointIndexInLine, std::sqrt(minScreenDist));
} else {
LOG_INFO("[CloudView] No point selected, minScreenDist=%.1f\n", std::sqrt(minScreenDist));
}
return result;
}
void PointCloudGLWidget::drawSelectedPoints()
{
if (m_selectedPoints.isEmpty()) {
return;
}
glPointSize(10.0f);
glDisable(GL_DEPTH_TEST);
glBegin(GL_POINTS);
for (const auto& pt : m_selectedPoints) {
if (pt.valid) {
glColor3f(1.0f, 0.5f, 0.0f);
glVertex3f(pt.x, pt.y, pt.z);
}
}
glEnd();
glEnable(GL_DEPTH_TEST);
glPointSize(m_pointSize);
}
void PointCloudGLWidget::drawMeasurementLine()
{
if (m_selectedPoints.size() < 2) {
return;
}
const auto& p1 = m_selectedPoints[0];
const auto& p2 = m_selectedPoints[1];
if (!p1.valid || !p2.valid) {
return;
}
glDisable(GL_DEPTH_TEST);
glLineWidth(2.0f);
glBegin(GL_LINES);
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(p1.x, p1.y, p1.z);
glVertex3f(p2.x, p2.y, p2.z);
glEnd();
glLineWidth(1.0f);
glEnable(GL_DEPTH_TEST);
}
void PointCloudGLWidget::drawSelectedLine()
{
if (!m_selectedLine.valid || m_selectedLine.cloudIndex < 0) {
return;
}
if (m_selectedLine.cloudIndex >= static_cast<int>(m_pointClouds.size())) {
return;
}
const auto& cloudData = m_pointClouds[m_selectedLine.cloudIndex];
if (!cloudData.hasLineInfo) {
return;
}
// 高亮显示选中线上的所有点
glPointSize(3.0f);
glDisable(GL_DEPTH_TEST);
glBegin(GL_POINTS);
glColor3f(1.0f, 1.0f, 0.0f); // 黄色高亮
if (m_selectedLine.mode == LineSelectMode::Vertical) {
// 纵向选线:显示同一条扫描线上的所有点
for (size_t i = 0; i < cloudData.lineIndices.size(); ++i) {
if (cloudData.lineIndices[i] == m_selectedLine.lineIndex) {
size_t vertIdx = i * 3;
if (vertIdx + 2 < cloudData.vertices.size()) {
glVertex3f(cloudData.vertices[vertIdx],
cloudData.vertices[vertIdx + 1],
cloudData.vertices[vertIdx + 2]);
}
}
}
} else {
// 横向选线:显示所有线的相同原始索引点
if (cloudData.pointsPerLine > 0 && m_selectedLine.pointIndex >= 0) {
for (size_t i = 0; i < cloudData.originalIndices.size(); ++i) {
int originalIdx = cloudData.originalIndices[i];
// 原始索引 % 每线点数 == 选中的点索引
if (originalIdx % cloudData.pointsPerLine == m_selectedLine.pointIndex) {
size_t vertIdx = i * 3;
if (vertIdx + 2 < cloudData.vertices.size()) {
glVertex3f(cloudData.vertices[vertIdx],
cloudData.vertices[vertIdx + 1],
cloudData.vertices[vertIdx + 2]);
}
}
}
}
}
glEnd();
glEnable(GL_DEPTH_TEST);
glPointSize(m_pointSize);
}
void PointCloudGLWidget::drawAxis()
{
// 在右下角绘制坐标系指示器
GLint viewport[4];
glGetIntegerv(GL_VIEWPORT, viewport);
int axisSize = 60; // 坐标系区域大小(像素)
int margin = 10; // 距离边缘的边距
int axisX = viewport[2] - axisSize - margin; // 右下角 X
int axisY = margin; // 右下角 YOpenGL Y 轴向上)
// 保存当前矩阵状态
glMatrixMode(GL_PROJECTION);
glPushMatrix();
glLoadIdentity();
// 设置正交投影,覆盖整个视口
glOrtho(0, viewport[2], 0, viewport[3], -100, 100);
glMatrixMode(GL_MODELVIEW);
glPushMatrix();
glLoadIdentity();
// 移动到右下角中心位置
glTranslatef(axisX + axisSize / 2.0f, axisY + axisSize / 2.0f, 0.0f);
// 应用当前视图的旋转(与主视图同步)
glRotatef(m_rotationX, 1, 0, 0);
glRotatef(m_rotationY, 0, 1, 0);
// 关闭深度测试,确保坐标系始终可见
glDisable(GL_DEPTH_TEST);
float axisLength = axisSize * 0.4f; // 坐标轴长度
glLineWidth(2.0f);
glBegin(GL_LINES);
// X 轴 - 红色
glColor3f(1.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(axisLength, 0.0f, 0.0f);
// Y 轴 - 绿色
glColor3f(0.0f, 1.0f, 0.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, axisLength, 0.0f);
// Z 轴 - 蓝色
glColor3f(0.0f, 0.0f, 1.0f);
glVertex3f(0.0f, 0.0f, 0.0f);
glVertex3f(0.0f, 0.0f, axisLength);
glEnd();
glLineWidth(1.0f);
// 恢复深度测试
glEnable(GL_DEPTH_TEST);
// 恢复矩阵状态
glPopMatrix();
glMatrixMode(GL_PROJECTION);
glPopMatrix();
glMatrixMode(GL_MODELVIEW);
}
void PointCloudGLWidget::mousePressEvent(QMouseEvent* event)
{
m_lastMousePos = event->pos();
if (event->button() == Qt::LeftButton) {
// Ctrl+左键:选点
if (event->modifiers() & Qt::ControlModifier) {
makeCurrent();
SelectedPointInfo point = pickPoint(event->pos().x(), event->pos().y());
doneCurrent();
if (point.valid) {
if (m_selectedPoints.size() >= MAX_SELECTED_POINTS) {
m_selectedPoints.clear();
}
m_selectedPoints.append(point);
emit pointSelected(point);
if (m_selectedPoints.size() == 2) {
float distance = calculateDistance(m_selectedPoints[0], m_selectedPoints[1]);
emit twoPointsSelected(m_selectedPoints[0], m_selectedPoints[1], distance);
}
update();
}
} else if (event->modifiers() & Qt::ShiftModifier) {
// Shift+左键:选线
makeCurrent();
SelectedPointInfo point = pickPoint(event->pos().x(), event->pos().y());
doneCurrent();
if (point.valid && point.lineIndex >= 0) {
if (m_lineSelectMode == LineSelectMode::Vertical) {
// 纵向选线:选中该点所在的线
m_selectedLine.valid = true;
m_selectedLine.cloudIndex = point.cloudIndex;
m_selectedLine.lineIndex = point.lineIndex;
m_selectedLine.pointIndex = -1;
m_selectedLine.mode = LineSelectMode::Vertical;
// 计算该线上的点数
int pointCount = 0;
if (point.cloudIndex >= 0 && point.cloudIndex < static_cast<int>(m_pointClouds.size())) {
const auto& cloudData = m_pointClouds[point.cloudIndex];
for (int idx : cloudData.lineIndices) {
if (idx == point.lineIndex) {
pointCount++;
}
}
}
m_selectedLine.pointCount = pointCount;
} else {
// 横向选线:选中所有线的相同索引点
m_selectedLine.valid = true;
m_selectedLine.cloudIndex = point.cloudIndex;
m_selectedLine.lineIndex = -1;
m_selectedLine.pointIndex = point.pointIndexInLine;
m_selectedLine.mode = LineSelectMode::Horizontal;
// 横向线的点数等于总线数
if (point.cloudIndex >= 0 && point.cloudIndex < static_cast<int>(m_pointClouds.size())) {
m_selectedLine.pointCount = m_pointClouds[point.cloudIndex].totalLines;
}
}
emit lineSelected(m_selectedLine);
update();
}
} else {
m_leftButtonPressed = true;
}
} else if (event->button() == Qt::RightButton) {
m_rightButtonPressed = true;
} else if (event->button() == Qt::MiddleButton) {
m_middleButtonPressed = true;
}
}
void PointCloudGLWidget::mouseMoveEvent(QMouseEvent* event)
{
QPoint delta = event->pos() - m_lastMousePos;
m_lastMousePos = event->pos();
if (m_leftButtonPressed) {
m_rotationY += delta.x() * 0.5f;
m_rotationX += delta.y() * 0.5f;
// 移除角度限制,允许无限旋转
update();
} else if (m_rightButtonPressed || m_middleButtonPressed) {
float factor = m_distance * 0.002f;
m_pan.setX(m_pan.x() - delta.x() * factor);
m_pan.setY(m_pan.y() + delta.y() * factor);
update();
}
}
void PointCloudGLWidget::mouseReleaseEvent(QMouseEvent* event)
{
if (event->button() == Qt::LeftButton) {
m_leftButtonPressed = false;
} else if (event->button() == Qt::RightButton) {
m_rightButtonPressed = false;
} else if (event->button() == Qt::MiddleButton) {
m_middleButtonPressed = false;
}
}
void PointCloudGLWidget::wheelEvent(QWheelEvent* event)
{
float delta = event->angleDelta().y() / 120.0f;
m_distance *= (1.0f - delta * 0.1f);
m_distance = qBound(0.1f, m_distance, 10000.0f);
update();
}
bool PointCloudGLWidget::getFirstCloudData(PointCloudXYZ& cloud) const
{
if (m_pointClouds.empty()) {
return false;
}
const auto& cloudData = m_pointClouds[0];
cloud.clear();
cloud.reserve(cloudData.vertices.size() / 3);
for (size_t i = 0; i < cloudData.vertices.size(); i += 3) {
Point3D pt;
pt.x = cloudData.vertices[i];
pt.y = cloudData.vertices[i + 1];
pt.z = cloudData.vertices[i + 2];
int lineIdx = 0;
if (cloudData.hasLineInfo && (i / 3) < cloudData.lineIndices.size()) {
lineIdx = cloudData.lineIndices[i / 3];
}
cloud.push_back(pt, lineIdx);
}
return true;
}
void PointCloudGLWidget::replaceFirstCloud(const PointCloudXYZ& cloud, const QString& name)
{
if (m_pointClouds.empty()) {
addPointCloud(cloud, name);
return;
}
// 保留第一个点云的颜色索引
int colorIndex = m_pointClouds[0].colorIndex;
// 重新构建数据
PointCloudData data;
data.name = name;
data.hasColor = false;
data.hasLineInfo = !cloud.lineIndices.empty();
data.colorIndex = colorIndex;
data.vertices.reserve(cloud.size() * 3);
data.totalLines = 0;
data.pointsPerLine = 0;
const float EPSILON = 1e-6f;
for (size_t i = 0; i < cloud.points.size(); ++i) {
const auto& pt = cloud.points[i];
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
continue;
}
// 显示时过滤 (0,0,0) 点
if (std::fabs(pt.x) < EPSILON && std::fabs(pt.y) < EPSILON && std::fabs(pt.z) < EPSILON) {
continue;
}
data.vertices.push_back(pt.x);
data.vertices.push_back(pt.y);
data.vertices.push_back(pt.z);
// 保存原始索引
data.originalIndices.push_back(static_cast<int>(i));
if (data.hasLineInfo && i < cloud.lineIndices.size()) {
int lineIdx = cloud.lineIndices[i];
data.lineIndices.push_back(lineIdx);
if (lineIdx + 1 > data.totalLines) {
data.totalLines = lineIdx + 1;
}
}
}
// 计算每线点数
if (data.totalLines > 0) {
data.pointsPerLine = static_cast<int>(cloud.size()) / data.totalLines;
}
m_pointClouds[0] = std::move(data);
computeBoundingBox();
resetView();
update();
}