Compare commits

..

No commits in common. "master" and "CloudView_1.1.0" have entirely different histories.

9 changed files with 118 additions and 392 deletions

View File

@ -54,11 +54,6 @@ public:
std::vector<std::vector<SVzNL3DPosition>>& scanLines, std::vector<std::vector<SVzNL3DPosition>>& scanLines,
size_t* validPointCount = nullptr); size_t* validPointCount = nullptr);
// 从文件加载Poly折线数据带颜色
// polyLines: 每条折线的点坐标列表polyLines[i] 是第i条折线的所有点含RGB颜色
int LoadPolySegments(const std::string& fileName,
std::vector<std::vector<SVzNLPointXYZRGBA>>& polyLines);
// 获取最后的错误信息 // 获取最后的错误信息
std::string GetLastError() const { return m_lastError; } std::string GetLastError() const { return m_lastError; }

View File

@ -102,21 +102,6 @@ int LaserDataLoader::LoadLaserScanData(const std::string& fileName,
nLaserPointIdx = 0; nLaserPointIdx = 0;
bFindLineNum = false; bFindLineNum = false;
} else if (line.find("Poly_") == 0) {
// 跳过 Poly 折线头及后续点坐标数据
size_t lastUnderscore = line.rfind('_');
int polyNum = 0;
if (lastUnderscore != std::string::npos && lastUnderscore + 1 < line.size()) {
polyNum = std::atoi(line.c_str() + lastUnderscore + 1);
}
for (int skip = 0; skip < polyNum; ) {
if (!std::getline(inputFile, line)) break;
TrimCarriageReturn(line);
if (!line.empty()) {
++skip;
}
}
} else if (line.find("{") == 0) { } else if (line.find("{") == 0) {
// 使用正则表达式判断是XYZ还是RGBD格式 // 使用正则表达式判断是XYZ还是RGBD格式
// XYZ格式: {x,y,z}-{leftX,leftY}-{rightX,rightY} // XYZ格式: {x,y,z}-{leftX,leftY}-{rightX,rightY}
@ -293,144 +278,6 @@ int LaserDataLoader::DebugSaveLaser(std::string fileName, std::vector<std::vecto
} }
} }
int LaserDataLoader::LoadPolySegments(const std::string& fileName,
std::vector<std::vector<SVzNLPointXYZRGBA>>& polyLines)
{
LOG_INFO("Loading poly segments from file: %s\n", fileName.c_str());
polyLines.clear();
std::ifstream inputFile(fileName);
if (!inputFile.is_open()) {
m_lastError = "Cannot open file: " + fileName;
LOG_ERROR("Cannot open file: %s\n", fileName.c_str());
return ERR_CODE(FILE_ERR_NOEXIST);
}
std::string line;
while (std::getline(inputFile, line)) {
TrimCarriageReturn(line);
// 跳过空行和注释
if (line.empty() || line[0] == '#') {
continue;
}
// 查找 Poly_index_num 头
if (line.find("Poly_") != 0) {
continue;
}
// 解析点数量(最后一个下划线后的数字)
size_t lastUnderscore = line.rfind('_');
if (lastUnderscore == std::string::npos || lastUnderscore + 1 >= line.size()) {
LOG_WARN("[LaserDataLoader] Invalid Poly header: %s\n", line.c_str());
continue;
}
int num = std::atoi(line.c_str() + lastUnderscore + 1);
if (num < 2) {
LOG_WARN("[LaserDataLoader] Invalid point count in Poly header: %s\n", line.c_str());
continue;
}
// 读取 num 个点坐标
std::vector<SVzNLPointXYZRGBA> points;
for (int i = 0; i < num; ) {
if (!std::getline(inputFile, line)) break;
TrimCarriageReturn(line);
if (line.empty()) {
continue; // 跳过空行,不计数
}
SVzNLPointXYZRGBA pt;
memset(&pt, 0, sizeof(pt));
pt.nRGB = 0x00FFFFFF; // 默认白色
bool parsed = false;
// 尝试花括号格式
if (line.find('{') != std::string::npos) {
float fx, fy, fz;
int R = 255, G = 255, B = 255, A = 255;
// 新RGBD格式: {x,y,z}-{lx,ly}-{rx,ry}-{R,G,B,A}
int cnt = sscanf(line.c_str(),
" { %f , %f , %f } - { %*f , %*f } - { %*f , %*f } - { %d , %d , %d , %d }",
&fx, &fy, &fz, &R, &G, &B, &A);
if (cnt >= 3) {
pt.x = fx;
pt.y = fy;
pt.z = fz;
if (cnt >= 7) {
// 解析到了颜色 R,G,B,A0-255Alpha 存储在高 8 位
unsigned int nRGB = static_cast<unsigned int>(A) << 24;
nRGB |= static_cast<unsigned int>(B) << 16;
nRGB |= static_cast<unsigned int>(G) << 8;
nRGB |= static_cast<unsigned int>(R);
pt.nRGB = nRGB;
}
parsed = true;
}
// 旧RGBD格式: {x,y,z,r,g,b}-{lx,ly}-{rx,ry}
if (!parsed) {
float r, g, b;
if (sscanf(line.c_str(), " { %f , %f , %f , %f , %f , %f }",
&fx, &fy, &fz, &r, &g, &b) == 6) {
pt.x = fx;
pt.y = fy;
pt.z = fz;
int nRGB = (int)(b * 255); nRGB <<= 8;
nRGB += (int)(g * 255); nRGB <<= 8;
nRGB += (int)(r * 255);
pt.nRGB = nRGB;
parsed = true;
}
}
// XYZ格式: {x,y,z}-{...}-{...}
if (!parsed) {
if (sscanf(line.c_str(), " { %f , %f , %f }", &fx, &fy, &fz) == 3) {
pt.x = fx;
pt.y = fy;
pt.z = fz;
parsed = true;
}
}
}
// 尝试纯数字格式: x y z空格/Tab/逗号分隔)
if (!parsed) {
float fx, fy, fz;
std::string lineCopy = line;
for (char& c : lineCopy) {
if (c == ',' || c == '\t') c = ' ';
}
if (sscanf(lineCopy.c_str(), "%f %f %f", &fx, &fy, &fz) == 3) {
pt.x = fx;
pt.y = fy;
pt.z = fz;
parsed = true;
}
}
if (parsed) {
points.push_back(pt);
}
++i;
}
if (points.size() >= 2) {
polyLines.push_back(std::move(points));
}
}
inputFile.close();
LOG_INFO("Loaded %zu poly lines from file: %s\n", polyLines.size(), fileName.c_str());
return SUCCESS;
}
void LaserDataLoader::FreeLaserScanData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines) void LaserDataLoader::FreeLaserScanData(std::vector<std::pair<EVzResultDataType, SVzLaserLineData>>& laserLines)
{ {
LOG_DEBUG("Freeing unified laser scan data, line count: %zu\n", laserLines.size()); LOG_DEBUG("Freeing unified laser scan data, line count: %zu\n", laserLines.size());
@ -640,38 +487,10 @@ int LaserDataLoader::_ParseLaserScanPoint(const std::string& data, SVzNL3DPositi
int LaserDataLoader::_ParseLaserScanPoint(const std::string& data, SVzNLPointXYZRGBA& sData, SVzNL2DLRPoint& s2DData) int LaserDataLoader::_ParseLaserScanPoint(const std::string& data, SVzNLPointXYZRGBA& sData, SVzNL2DLRPoint& s2DData)
{ {
float X, Y, Z; float X, Y, Z;
float r, g, b;
float leftX, leftY; float leftX, leftY;
float rightX, rightY; float rightX, rightY;
sscanf(data.c_str(), "{%f,%f,%f,%f,%f,%f}-{%f,%f}-{%f,%f}", &X, &Y, &Z, &r, &g, &b, &leftX, &leftY, &rightX, &rightY);
// 尝试新格式: {x,y,z}-{lx,ly}-{rx,ry}-{R,G,B,A}
int R, G, B, A;
int parsed = sscanf(data.c_str(),
" { %f , %f , %f } - { %f , %f } - { %f , %f } - { %d , %d , %d , %d }",
&X, &Y, &Z, &leftX, &leftY, &rightX, &rightY, &R, &G, &B, &A);
if (parsed >= 11) {
sData.x = X;
sData.y = Y;
sData.z = Z;
// R,G,B,A 是 0-255 整数值Alpha 存储在高 8 位
unsigned int nRGB = static_cast<unsigned int>(A) << 24;
nRGB |= static_cast<unsigned int>(B) << 16;
nRGB |= static_cast<unsigned int>(G) << 8;
nRGB |= static_cast<unsigned int>(R);
sData.nRGB = nRGB;
s2DData.sLeft.x = leftX;
s2DData.sLeft.y = leftY;
s2DData.sRight.x = rightX;
s2DData.sRight.y = rightY;
return SUCCESS;
}
// 旧格式: {x,y,z,r,g,b}-{lx,ly}-{rx,ry} r,g,b 为 0~1 浮点值
float r, g, b;
sscanf(data.c_str(),
" { %f , %f , %f , %f , %f , %f } - { %f , %f } - { %f , %f }",
&X, &Y, &Z, &r, &g, &b, &leftX, &leftY, &rightX, &rightY);
sData.x = X; sData.x = X;
sData.y = Y; sData.y = Y;
sData.z = Z; sData.z = Z;
@ -705,38 +524,27 @@ int LaserDataLoader::_GetLaserType(const std::string& fileName, EVzResultDataTyp
bool bFind = false; bool bFind = false;
while (std::getline(inputFile, linedata)) { while (std::getline(inputFile, linedata)) {
// 去除行末的 \r 字符(处理 Windows 格式的 CR LF 换行符)
TrimCarriageReturn(linedata); TrimCarriageReturn(linedata);
if (linedata.find("{") == 0) { if (linedata.find("{") == 0) {
// 统计 { 的数量判断格式 // 修复正则表达式以匹配实际数据格式
int braceCount = 0; // XYZ格式: {x,y,z}-{leftX,leftY}-{rightX,rightY}
for (char c : linedata) { // RGBD格式: {x,y,z,r,g,b}-{leftX,leftY}-{rightX,rightY}
if (c == '{') braceCount++; // 更宽松的正则表达式,允许更多的空格变化
} std::regex xyzPattern(R"(\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\})");
std::regex rgbdPattern(R"(\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\}\s*-\s*\{\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*,\s*[+-]?(?:\d+\.?\d*|\.\d+)\s*\})");
if (braceCount == 4) { // 先尝试匹配RGBD格式6个数字
// 新RGBD格式: {x,y,z}-{lx,ly}-{rx,ry}-{R,G,B,A} if (std::regex_match(linedata, rgbdPattern)) {
eDataType = keResultDataType_PointXYZRGBA; eDataType = keResultDataType_PointXYZRGBA;
bFind = true; bFind = true;
} else if (braceCount == 3) {
// 检查第一组的逗号数量区分XYZ和旧RGBD
size_t firstClose = linedata.find('}');
if (firstClose != std::string::npos) {
int commaCount = 0;
for (size_t i = 0; i < firstClose; ++i) {
if (linedata[i] == ',') commaCount++;
} }
if (commaCount >= 5) { // 再尝试匹配XYZ格式3个数字
// 旧RGBD格式: {x,y,z,r,g,b}-{lx,ly}-{rx,ry} else if (std::regex_match(linedata, xyzPattern)) {
eDataType = keResultDataType_PointXYZRGBA;
bFind = true;
} else if (commaCount >= 2) {
// XYZ格式: {x,y,z}-{lx,ly}-{rx,ry}
eDataType = keResultDataType_Position; eDataType = keResultDataType_Position;
bFind = true; bFind = true;
} }
}
}
break; break;
} }
} }

View File

@ -44,6 +44,11 @@ private slots:
*/ */
void onOpenFile(); void onOpenFile();
/**
* @brief 线
*/
void onOpenSegmentFile();
/** /**
* @brief 姿 * @brief 姿
*/ */
@ -220,18 +225,6 @@ private:
*/ */
void updateLinePointsDialog(); void updateLinePointsDialog();
/**
* @brief
* @return
*/
bool loadPointCloudFile(const QString& fileName);
/**
* @brief 线Poly格式
* @return 线
*/
bool loadSegmentFile(const QString& fileName);
// 点云显示控件 // 点云显示控件
PointCloudGLWidget* m_glWidget; PointCloudGLWidget* m_glWidget;
@ -240,6 +233,7 @@ private:
// 文件操作控件 // 文件操作控件
QPushButton* m_btnOpenFile; QPushButton* m_btnOpenFile;
QPushButton* m_btnOpenSegment;
QPushButton* m_btnOpenPose; QPushButton* m_btnOpenPose;
QPushButton* m_btnClearAll; QPushButton* m_btnClearAll;

View File

@ -22,10 +22,9 @@ struct Point3DRGB
{ {
float x, y, z; float x, y, z;
uint8_t r, g, b; uint8_t r, g, b;
float pointSize; // 自定义点大小0 表示使用全局默认值(来自 RGBA 中 A > 1 的值) Point3DRGB() : x(0), y(0), z(0), r(255), g(255), b(255) {}
Point3DRGB() : x(0), y(0), z(0), r(255), g(255), b(255), pointSize(0) {} Point3DRGB(float _x, float _y, float _z, uint8_t _r = 255, uint8_t _g = 255, uint8_t _b = 255)
Point3DRGB(float _x, float _y, float _z, uint8_t _r = 255, uint8_t _g = 255, uint8_t _b = 255, float _ps = 0) : x(_x), y(_y), z(_z), r(_r), g(_g), b(_b) {}
: x(_x), y(_y), z(_z), r(_r), g(_g), b(_b), pointSize(_ps) {}
}; };
/** /**

View File

@ -87,11 +87,10 @@ struct LineSegment
float x1, y1, z1; // 起点 float x1, y1, z1; // 起点
float x2, y2, z2; // 终点 float x2, y2, z2; // 终点
float r, g, b; // 颜色 (0-1) 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() : x1(0), y1(0), z1(0), x2(0), y2(0), z2(0), r(1), g(1), b(1) {}
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) 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)
: x1(_x1), y1(_y1), z1(_z1), x2(_x2), y2(_y2), z2(_z2), r(_r), g(_g), b(_b), lineWidth(_lw) {} : x1(_x1), y1(_y1), z1(_z1), x2(_x2), y2(_y2), z2(_z2), r(_r), g(_g), b(_b) {}
}; };
/** /**
@ -273,14 +272,6 @@ private:
int totalLines; // 总线数 int totalLines; // 总线数
int pointsPerLine; // 每线点数(网格化点云) int pointsPerLine; // 每线点数(网格化点云)
// 自定义点大小分组RGBA 中 A > 1 的点按大小分组)
struct PointSizeGroup {
float pointSize; // 点大小
std::vector<size_t> indices; // 该大小的点在顶点数组中的索引(顶点索引 = indices[i] * 3
};
std::vector<PointSizeGroup> customPointSizeGroups;
bool hasCustomPointSizes;
// VBO 缓冲区 // VBO 缓冲区
QOpenGLBuffer vertexBuffer; // 顶点 VBO QOpenGLBuffer vertexBuffer; // 顶点 VBO
QOpenGLBuffer colorBuffer; // 颜色 VBO QOpenGLBuffer colorBuffer; // 颜色 VBO
@ -289,7 +280,6 @@ private:
PointCloudData() PointCloudData()
: hasColor(false), hasLineInfo(false), colorIndex(0) : hasColor(false), hasLineInfo(false), colorIndex(0)
, totalLines(0), pointsPerLine(0) , totalLines(0), pointsPerLine(0)
, hasCustomPointSizes(false)
, vertexBuffer(QOpenGLBuffer::VertexBuffer) , vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer) , colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false) , vboCreated(false)
@ -307,8 +297,6 @@ private:
, colorIndex(other.colorIndex) , colorIndex(other.colorIndex)
, totalLines(other.totalLines) , totalLines(other.totalLines)
, pointsPerLine(other.pointsPerLine) , pointsPerLine(other.pointsPerLine)
, customPointSizeGroups(std::move(other.customPointSizeGroups))
, hasCustomPointSizes(other.hasCustomPointSizes)
, vertexBuffer(QOpenGLBuffer::VertexBuffer) , vertexBuffer(QOpenGLBuffer::VertexBuffer)
, colorBuffer(QOpenGLBuffer::VertexBuffer) , colorBuffer(QOpenGLBuffer::VertexBuffer)
, vboCreated(false) , vboCreated(false)
@ -336,8 +324,6 @@ private:
colorIndex = other.colorIndex; colorIndex = other.colorIndex;
totalLines = other.totalLines; totalLines = other.totalLines;
pointsPerLine = other.pointsPerLine; pointsPerLine = other.pointsPerLine;
customPointSizeGroups = std::move(other.customPointSizeGroups);
hasCustomPointSizes = other.hasCustomPointSizes;
// VBO 需要重建 // VBO 需要重建
other.vboCreated = false; other.vboCreated = false;
} }

View File

@ -13,7 +13,6 @@
#include <QGridLayout> #include <QGridLayout>
#include <cmath> #include <cmath>
#include "VrLog.h" #include "VrLog.h"
#include "LaserDataLoader.h"
CloudViewMainWindow::CloudViewMainWindow(QWidget* parent) CloudViewMainWindow::CloudViewMainWindow(QWidget* parent)
: QMainWindow(parent) : QMainWindow(parent)
@ -126,12 +125,18 @@ QGroupBox* CloudViewMainWindow::createFileGroup()
layout->setSpacing(3); layout->setSpacing(3);
layout->setContentsMargins(5, 5, 5, 5); layout->setContentsMargins(5, 5, 5, 5);
m_btnOpenFile = new QPushButton("打开文件", group); m_btnOpenFile = new QPushButton("打开点云", group);
m_btnOpenFile->setMinimumHeight(24); m_btnOpenFile->setMinimumHeight(24);
m_btnOpenFile->setMaximumHeight(24); m_btnOpenFile->setMaximumHeight(24);
connect(m_btnOpenFile, &QPushButton::clicked, this, &CloudViewMainWindow::onOpenFile); connect(m_btnOpenFile, &QPushButton::clicked, this, &CloudViewMainWindow::onOpenFile);
layout->addWidget(m_btnOpenFile); layout->addWidget(m_btnOpenFile);
m_btnOpenSegment = new QPushButton("打开线段 {x,y,z}-{x,y,z}", group);
m_btnOpenSegment->setMinimumHeight(24);
m_btnOpenSegment->setMaximumHeight(24);
connect(m_btnOpenSegment, &QPushButton::clicked, this, &CloudViewMainWindow::onOpenSegmentFile);
layout->addWidget(m_btnOpenSegment);
m_btnOpenPose = new QPushButton("打开姿态点 {x,y,z}-{r,p,y}", group); m_btnOpenPose = new QPushButton("打开姿态点 {x,y,z}-{r,p,y}", group);
m_btnOpenPose->setMinimumHeight(24); m_btnOpenPose->setMinimumHeight(24);
m_btnOpenPose->setMaximumHeight(24); m_btnOpenPose->setMaximumHeight(24);
@ -518,7 +523,6 @@ QGroupBox* CloudViewMainWindow::createLineGroup()
m_btnSelectByNumber->setMaximumHeight(24); m_btnSelectByNumber->setMaximumHeight(24);
m_btnSelectByNumber->setMaximumWidth(50); m_btnSelectByNumber->setMaximumWidth(50);
connect(m_btnSelectByNumber, &QPushButton::clicked, this, &CloudViewMainWindow::onSelectLineByNumber); connect(m_btnSelectByNumber, &QPushButton::clicked, this, &CloudViewMainWindow::onSelectLineByNumber);
connect(m_lineNumberInput, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onSelectLineByNumber);
inputLayout->addWidget(m_lineNumberInput, 1); inputLayout->addWidget(m_lineNumberInput, 1);
inputLayout->addWidget(m_btnSelectByNumber); inputLayout->addWidget(m_btnSelectByNumber);
layout->addLayout(inputLayout); layout->addLayout(inputLayout);
@ -575,42 +579,17 @@ QGroupBox* CloudViewMainWindow::createCloudListGroup()
void CloudViewMainWindow::onOpenFile() void CloudViewMainWindow::onOpenFile()
{ {
// 如果已有打开的文件,提示是否清除
if (m_cloudList->count() > 0) {
auto ret = QMessageBox::question(this, "提示",
"当前已有打开的文件,是否清除?",
QMessageBox::Yes | QMessageBox::No | QMessageBox::Cancel);
if (ret == QMessageBox::Cancel) {
return;
}
if (ret == QMessageBox::Yes) {
onClearAll();
}
}
QString fileName = QFileDialog::getOpenFileName( QString fileName = QFileDialog::getOpenFileName(
this, this,
"打开文件", "打开点云文件",
QString(), QString(),
"所有支持格式 (*.pcd *.txt);;PCD 文件 (*.pcd);;TXT 文件 (*.txt);;所有文件 (*.*)" "点云文件 (*.pcd *.txt);;PCD 文件 (*.pcd);;TXT 文件 (*.txt);;所有文件 (*.*)"
); );
if (fileName.isEmpty()) { if (fileName.isEmpty()) {
return; return;
} }
// 同一文件中可能同时包含点云和线段,两者都尝试加载
bool cloudOk = loadPointCloudFile(fileName);
bool segmentOk = loadSegmentFile(fileName);
if (!cloudOk && !segmentOk) {
QMessageBox::critical(this, "错误", "无法从文件中加载点云或线段数据");
statusBar()->showMessage("加载失败");
}
}
bool CloudViewMainWindow::loadPointCloudFile(const QString& fileName)
{
statusBar()->showMessage("正在加载点云..."); statusBar()->showMessage("正在加载点云...");
QFileInfo fileInfo(fileName); QFileInfo fileInfo(fileName);
@ -622,8 +601,9 @@ bool CloudViewMainWindow::loadPointCloudFile(const QString& fileName)
int result = m_converter->loadFromFile(fileName.toStdString(), rgbCloud); int result = m_converter->loadFromFile(fileName.toStdString(), rgbCloud);
if (result != 0) { if (result != 0) {
LOG_INFO("[CloudView] Load point cloud failed: %s\n", m_converter->getLastError().c_str()); QMessageBox::critical(this, "错误", QString("加载点云失败: %1").arg(QString::fromStdString(m_converter->getLastError())));
return false; statusBar()->showMessage("加载失败");
return;
} }
// 保存原始完整点云 XYZ用于旋转/线上点等功能) // 保存原始完整点云 XYZ用于旋转/线上点等功能)
@ -652,9 +632,11 @@ bool CloudViewMainWindow::loadPointCloudFile(const QString& fileName)
} }
if (hadColor) { if (hadColor) {
// 有颜色数据:使用 addPointCloud(PointCloudXYZRGB) 显示原始颜色
m_glWidget->addPointCloud(rgbCloud, cloudName); m_glWidget->addPointCloud(rgbCloud, cloudName);
LOG_INFO("[CloudView] Loaded with original color, points: %zu\n", rgbCloud.size()); LOG_INFO("[CloudView] Loaded with original color, points: %zu\n", rgbCloud.size());
} else { } else {
// 无颜色数据:使用 addPointCloud(PointCloudXYZ) 显示(颜色表轮换)
m_glWidget->addPointCloud(m_originalCloud, cloudName); m_glWidget->addPointCloud(m_originalCloud, cloudName);
LOG_INFO("[CloudView] Loaded without color (color table), points: %zu\n", m_originalCloud.size()); LOG_INFO("[CloudView] Loaded without color (color table), points: %zu\n", m_originalCloud.size());
} }
@ -682,63 +664,86 @@ bool CloudViewMainWindow::loadPointCloudFile(const QString& fileName)
m_cloudList->addItem(itemText); m_cloudList->addItem(itemText);
statusBar()->showMessage(QString("已加载 %1 个点%2").arg(m_converter->getLoadedPointCount()).arg(hadColor ? " (彩色)" : "")); statusBar()->showMessage(QString("已加载 %1 个点%2").arg(m_converter->getLoadedPointCount()).arg(hadColor ? " (彩色)" : ""));
return true;
} }
bool CloudViewMainWindow::loadSegmentFile(const QString& fileName) void CloudViewMainWindow::onOpenSegmentFile()
{ {
QString fileName = QFileDialog::getOpenFileName(
this,
"打开线段文件",
QString(),
"文本文件 (*.txt);;所有文件 (*.*)"
);
if (fileName.isEmpty()) {
return;
}
statusBar()->showMessage("正在加载线段..."); statusBar()->showMessage("正在加载线段...");
LaserDataLoader loader; QFile file(fileName);
std::vector<std::vector<SVzNLPointXYZRGBA>> polyLines; if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
int result = loader.LoadPolySegments(fileName.toStdString(), polyLines); QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName));
statusBar()->showMessage("加载失败");
if (result != 0) { return;
LOG_INFO("[CloudView] Load segments failed: %s\n", loader.GetLastError().c_str());
return false;
} }
if (polyLines.empty()) {
return false;
}
// 将折线点转换为线段(使用点自带的颜色)
QVector<LineSegment> segments; QVector<LineSegment> segments;
for (const auto& polyLine : polyLines) { QTextStream in(&file);
for (size_t i = 0; i + 1 < polyLine.size(); ++i) { int lineNum = 0;
const auto& p1 = polyLine[i]; int validCount = 0;
const auto& p2 = polyLine[i + 1];
// 取起点颜色作为线段颜色nRGB 为 (A<<24)|BGR 打包格式 while (!in.atEnd()) {
float r = ((p1.nRGB >> 0) & 0xFF) / 255.0f; QString line = in.readLine().trimmed();
float g = ((p1.nRGB >> 8) & 0xFF) / 255.0f; lineNum++;
float b = ((p1.nRGB >> 16) & 0xFF) / 255.0f;
uint8_t a = static_cast<uint8_t>((p1.nRGB >> 24) & 0xFF); // 跳过空行和注释
// A > 1 时作为线宽使用 if (line.isEmpty() || line.startsWith('#')) {
float lineWidth = (a > 1) ? static_cast<float>(a) : 0.0f; continue;
segments.append(LineSegment(
p1.x, p1.y, p1.z,
p2.x, p2.y, p2.z,
r, g, b, lineWidth));
} }
// 解析格式:{x,y,z}-{x,y,z}
QRegExp regex("\\{([^}]+)\\}-\\{([^}]+)\\}");
if (regex.indexIn(line) == -1) {
LOG_WARN("[CloudView] Line %d: Invalid format, expected {x,y,z}-{x,y,z}\n", lineNum);
continue;
} }
QString point1Str = regex.cap(1);
QString point2Str = regex.cap(2);
QStringList p1 = point1Str.split(',');
QStringList p2 = point2Str.split(',');
if (p1.size() != 3 || p2.size() != 3) {
LOG_WARN("[CloudView] Line %d: Invalid point format\n", lineNum);
continue;
}
bool ok = true;
float x1 = p1[0].toFloat(&ok); if (!ok) continue;
float y1 = p1[1].toFloat(&ok); if (!ok) continue;
float z1 = p1[2].toFloat(&ok); if (!ok) continue;
float x2 = p2[0].toFloat(&ok); if (!ok) continue;
float y2 = p2[1].toFloat(&ok); if (!ok) continue;
float z2 = p2[2].toFloat(&ok); if (!ok) continue;
// 默认白色
segments.append(LineSegment(x1, y1, z1, x2, y2, z2, 1.0f, 1.0f, 1.0f));
validCount++;
}
file.close();
if (segments.isEmpty()) { if (segments.isEmpty()) {
return false; QMessageBox::warning(this, "警告", "文件中没有有效的线段数据");
statusBar()->showMessage("加载失败");
return;
} }
m_glWidget->addLineSegments(segments); m_glWidget->addLineSegments(segments);
statusBar()->showMessage(QString("已加载 %1 条线段").arg(validCount));
// 添加到列表 LOG_INFO("[CloudView] Loaded %d line segments from %s\n", validCount, fileName.toStdString().c_str());
QFileInfo fileInfo(fileName);
int totalPolyCount = static_cast<int>(polyLines.size());
QString itemName = QString("Segments (%1)").arg(fileInfo.fileName());
m_cloudList->addItem(QString("%1 - %2 条折线, %3 条线段")
.arg(itemName).arg(totalPolyCount).arg(segments.size()));
statusBar()->showMessage(QString("已加载 %1 条折线, %2 条线段").arg(totalPolyCount).arg(segments.size()));
LOG_INFO("[CloudView] Loaded %d polylines, %d segments from %s\n",
totalPolyCount, segments.size(), fileName.toStdString().c_str());
return true;
} }
void CloudViewMainWindow::onOpenPoseFile() void CloudViewMainWindow::onOpenPoseFile()
@ -826,8 +831,6 @@ void CloudViewMainWindow::onOpenPoseFile()
void CloudViewMainWindow::onClearAll() void CloudViewMainWindow::onClearAll()
{ {
m_glWidget->clearPointClouds(); m_glWidget->clearPointClouds();
m_glWidget->clearLineSegments();
m_glWidget->clearPosePoints();
m_cloudList->clear(); m_cloudList->clear();
m_cloudCount = 0; m_cloudCount = 0;
m_currentLineNum = 0; m_currentLineNum = 0;

View File

@ -137,15 +137,12 @@ int PointCloudConverter::loadFromTxt(const std::string& fileName, PointCloudXYZR
for (const auto& line : rgbdData) { for (const auto& line : rgbdData) {
for (int i = 0; i < line.nPointCnt; ++i) { for (int i = 0; i < line.nPointCnt; ++i) {
const SVzNLPointXYZRGBA& pt = line.p3DPoint[i]; const SVzNLPointXYZRGBA& pt = line.p3DPoint[i];
// 解包颜色nRGB 格式为 (A << 24) | (B << 16) | (G << 8) | R // 解包颜色nRGB 格式为 (B << 16) | (G << 8) | R
uint8_t r = static_cast<uint8_t>(pt.nRGB & 0xFF); uint8_t r = static_cast<uint8_t>(pt.nRGB & 0xFF);
uint8_t g = static_cast<uint8_t>((pt.nRGB >> 8) & 0xFF); uint8_t g = static_cast<uint8_t>((pt.nRGB >> 8) & 0xFF);
uint8_t b = static_cast<uint8_t>((pt.nRGB >> 16) & 0xFF); uint8_t b = static_cast<uint8_t>((pt.nRGB >> 16) & 0xFF);
uint8_t a = static_cast<uint8_t>((pt.nRGB >> 24) & 0xFF);
// A > 1 时作为点大小使用
float pointSize = (a > 1) ? static_cast<float>(a) : 0.0f;
Point3DRGB point(pt.x, pt.y, pt.z, r, g, b, pointSize); Point3DRGB point(pt.x, pt.y, pt.z, r, g, b);
cloud.push_back(point, lineIndex); cloud.push_back(point, lineIndex);
totalCount++; totalCount++;
} }

View File

@ -2,7 +2,6 @@
#include <QDebug> #include <QDebug>
#include <QtMath> #include <QtMath>
#include <cfloat> #include <cfloat>
#include <map>
#include "VrLog.h" #include "VrLog.h"
// OpenGL/GLU 头文件 // OpenGL/GLU 头文件
@ -197,27 +196,6 @@ void PointCloudGLWidget::paintGL()
glDisableClientState(GL_VERTEX_ARRAY); glDisableClientState(GL_VERTEX_ARRAY);
glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_COLOR_ARRAY);
// 绘制自定义大小的点RGBA 中 A > 1 的点)
if (cloudData.hasCustomPointSizes) {
glDepthFunc(GL_LEQUAL); // 允许在相同深度覆盖绘制
for (const auto& group : cloudData.customPointSizeGroups) {
glPointSize(group.pointSize);
glBegin(GL_POINTS);
for (size_t idx : group.indices) {
size_t vi = idx * 3;
if (vi + 2 < cloudData.vertices.size()) {
if (cloudData.hasColor && vi + 2 < cloudData.colors.size()) {
glColor3f(cloudData.colors[vi], cloudData.colors[vi + 1], cloudData.colors[vi + 2]);
}
glVertex3f(cloudData.vertices[vi], cloudData.vertices[vi + 1], cloudData.vertices[vi + 2]);
}
}
glEnd();
}
glDepthFunc(GL_LESS); // 恢复默认深度测试
glPointSize(m_pointSize); // 恢复默认点大小
}
} }
drawSelectedPoints(); drawSelectedPoints();
@ -318,19 +296,12 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QStr
data.colors.reserve(cloud.size() * 3); data.colors.reserve(cloud.size() * 3);
data.totalLines = 0; data.totalLines = 0;
data.pointsPerLine = 0; data.pointsPerLine = 0;
data.hasCustomPointSizes = false;
// 用于按点大小分组的临时 map
std::map<float, std::vector<size_t>> sizeGroupMap;
for (size_t i = 0; i < cloud.points.size(); ++i) { for (size_t i = 0; i < cloud.points.size(); ++i) {
const auto& pt = cloud.points[i]; const auto& pt = cloud.points[i];
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) { if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
continue; continue;
} }
size_t pointIndex = data.vertices.size() / 3;
data.vertices.push_back(pt.x); data.vertices.push_back(pt.x);
data.vertices.push_back(pt.y); data.vertices.push_back(pt.y);
data.vertices.push_back(pt.z); data.vertices.push_back(pt.z);
@ -350,23 +321,6 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QStr
data.totalLines = lineIdx + 1; data.totalLines = lineIdx + 1;
} }
} }
// 收集自定义点大小A > 1
if (pt.pointSize > 1.0f) {
sizeGroupMap[pt.pointSize].push_back(pointIndex);
}
}
// 构建自定义点大小分组
if (!sizeGroupMap.empty()) {
data.hasCustomPointSizes = true;
for (auto& pair : sizeGroupMap) {
PointCloudData::PointSizeGroup group;
group.pointSize = pair.first;
group.indices = std::move(pair.second);
data.customPointSizeGroups.push_back(std::move(group));
}
LOG_INFO("[CloudView] Found %zu custom point size groups\n", data.customPointSizeGroups.size());
} }
// 计算每线点数 // 计算每线点数
@ -1233,26 +1187,16 @@ void PointCloudGLWidget::drawLineSegments()
return; return;
} }
// 按线宽分组绘制 glLineWidth(2.0f);
// 收集所有不同的线宽值0 表示默认 2.0f
std::map<float, QVector<int>> widthGroups;
for (int i = 0; i < m_lineSegments.size(); ++i) {
float w = m_lineSegments[i].lineWidth > 0 ? m_lineSegments[i].lineWidth : 2.0f;
widthGroups[w].append(i);
}
for (const auto& group : widthGroups) {
glLineWidth(group.first);
glBegin(GL_LINES); glBegin(GL_LINES);
for (int idx : group.second) {
const auto& seg = m_lineSegments[idx]; for (const auto& seg : m_lineSegments) {
glColor3f(seg.r, seg.g, seg.b); glColor3f(seg.r, seg.g, seg.b);
glVertex3f(seg.x1, seg.y1, seg.z1); glVertex3f(seg.x1, seg.y1, seg.z1);
glVertex3f(seg.x2, seg.y2, seg.z2); glVertex3f(seg.x2, seg.y2, seg.z2);
} }
glEnd();
}
glEnd();
glLineWidth(1.0f); glLineWidth(1.0f);
} }

View File

@ -3,7 +3,7 @@
#include <QIcon> #include <QIcon>
#include "CloudViewMainWindow.h" #include "CloudViewMainWindow.h"
#define APP_VERSION "1.1.2" #define APP_VERSION "1.1.0"
int main(int argc, char* argv[]) int main(int argc, char* argv[])
{ {