Compare commits
No commits in common. "master" and "CloudView_1.1.0" have entirely different histories.
master
...
CloudView_
@ -54,11 +54,6 @@ public:
|
||||
std::vector<std::vector<SVzNL3DPosition>>& scanLines,
|
||||
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; }
|
||||
|
||||
|
||||
@ -102,21 +102,6 @@ int LaserDataLoader::LoadLaserScanData(const std::string& fileName,
|
||||
nLaserPointIdx = 0;
|
||||
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) {
|
||||
// 使用正则表达式判断是XYZ还是RGBD格式
|
||||
// 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,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);
|
||||
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)
|
||||
{
|
||||
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)
|
||||
{
|
||||
float X, Y, Z;
|
||||
float r, g, b;
|
||||
float leftX, leftY;
|
||||
float 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);
|
||||
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.y = Y;
|
||||
sData.z = Z;
|
||||
@ -705,38 +524,27 @@ int LaserDataLoader::_GetLaserType(const std::string& fileName, EVzResultDataTyp
|
||||
|
||||
bool bFind = false;
|
||||
while (std::getline(inputFile, linedata)) {
|
||||
// 去除行末的 \r 字符(处理 Windows 格式的 CR LF 换行符)
|
||||
TrimCarriageReturn(linedata);
|
||||
|
||||
if (linedata.find("{") == 0) {
|
||||
// 统计 { 的数量判断格式
|
||||
int braceCount = 0;
|
||||
for (char c : linedata) {
|
||||
if (c == '{') braceCount++;
|
||||
}
|
||||
// 修复正则表达式以匹配实际数据格式
|
||||
// XYZ格式: {x,y,z}-{leftX,leftY}-{rightX,rightY}
|
||||
// RGBD格式: {x,y,z,r,g,b}-{leftX,leftY}-{rightX,rightY}
|
||||
// 更宽松的正则表达式,允许更多的空格变化
|
||||
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格式: {x,y,z}-{lx,ly}-{rx,ry}-{R,G,B,A}
|
||||
// 先尝试匹配RGBD格式(6个数字)
|
||||
if (std::regex_match(linedata, rgbdPattern)) {
|
||||
eDataType = keResultDataType_PointXYZRGBA;
|
||||
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) {
|
||||
// 旧RGBD格式: {x,y,z,r,g,b}-{lx,ly}-{rx,ry}
|
||||
eDataType = keResultDataType_PointXYZRGBA;
|
||||
bFind = true;
|
||||
} else if (commaCount >= 2) {
|
||||
// XYZ格式: {x,y,z}-{lx,ly}-{rx,ry}
|
||||
// 再尝试匹配XYZ格式(3个数字)
|
||||
else if (std::regex_match(linedata, xyzPattern)) {
|
||||
eDataType = keResultDataType_Position;
|
||||
bFind = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@ -44,6 +44,11 @@ private slots:
|
||||
*/
|
||||
void onOpenFile();
|
||||
|
||||
/**
|
||||
* @brief 打开线段文件
|
||||
*/
|
||||
void onOpenSegmentFile();
|
||||
|
||||
/**
|
||||
* @brief 打开姿态点文件
|
||||
*/
|
||||
@ -220,18 +225,6 @@ private:
|
||||
*/
|
||||
void updateLinePointsDialog();
|
||||
|
||||
/**
|
||||
* @brief 加载点云文件
|
||||
* @return 是否成功加载到点云数据
|
||||
*/
|
||||
bool loadPointCloudFile(const QString& fileName);
|
||||
|
||||
/**
|
||||
* @brief 加载线段文件(Poly格式)
|
||||
* @return 是否成功加载到线段数据
|
||||
*/
|
||||
bool loadSegmentFile(const QString& fileName);
|
||||
|
||||
// 点云显示控件
|
||||
PointCloudGLWidget* m_glWidget;
|
||||
|
||||
@ -240,6 +233,7 @@ private:
|
||||
|
||||
// 文件操作控件
|
||||
QPushButton* m_btnOpenFile;
|
||||
QPushButton* m_btnOpenSegment;
|
||||
QPushButton* m_btnOpenPose;
|
||||
QPushButton* m_btnClearAll;
|
||||
|
||||
|
||||
@ -22,10 +22,9 @@ struct Point3DRGB
|
||||
{
|
||||
float x, y, z;
|
||||
uint8_t r, g, b;
|
||||
float pointSize; // 自定义点大小,0 表示使用全局默认值(来自 RGBA 中 A > 1 的值)
|
||||
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, float _ps = 0)
|
||||
: x(_x), y(_y), z(_z), r(_r), g(_g), b(_b), pointSize(_ps) {}
|
||||
Point3DRGB() : x(0), y(0), z(0), r(255), g(255), b(255) {}
|
||||
Point3DRGB(float _x, float _y, float _z, uint8_t _r = 255, uint8_t _g = 255, uint8_t _b = 255)
|
||||
: x(_x), y(_y), z(_z), r(_r), g(_g), b(_b) {}
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@ -87,11 +87,10 @@ 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) {}
|
||||
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)
|
||||
: 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 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
|
||||
@ -289,7 +280,6 @@ private:
|
||||
PointCloudData()
|
||||
: hasColor(false), hasLineInfo(false), colorIndex(0)
|
||||
, totalLines(0), pointsPerLine(0)
|
||||
, hasCustomPointSizes(false)
|
||||
, vertexBuffer(QOpenGLBuffer::VertexBuffer)
|
||||
, colorBuffer(QOpenGLBuffer::VertexBuffer)
|
||||
, vboCreated(false)
|
||||
@ -307,8 +297,6 @@ private:
|
||||
, 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)
|
||||
@ -336,8 +324,6 @@ private:
|
||||
colorIndex = other.colorIndex;
|
||||
totalLines = other.totalLines;
|
||||
pointsPerLine = other.pointsPerLine;
|
||||
customPointSizeGroups = std::move(other.customPointSizeGroups);
|
||||
hasCustomPointSizes = other.hasCustomPointSizes;
|
||||
// VBO 需要重建
|
||||
other.vboCreated = false;
|
||||
}
|
||||
|
||||
@ -13,7 +13,6 @@
|
||||
#include <QGridLayout>
|
||||
#include <cmath>
|
||||
#include "VrLog.h"
|
||||
#include "LaserDataLoader.h"
|
||||
|
||||
CloudViewMainWindow::CloudViewMainWindow(QWidget* parent)
|
||||
: QMainWindow(parent)
|
||||
@ -126,12 +125,18 @@ QGroupBox* CloudViewMainWindow::createFileGroup()
|
||||
layout->setSpacing(3);
|
||||
layout->setContentsMargins(5, 5, 5, 5);
|
||||
|
||||
m_btnOpenFile = new QPushButton("打开文件", group);
|
||||
m_btnOpenFile = new QPushButton("打开点云", group);
|
||||
m_btnOpenFile->setMinimumHeight(24);
|
||||
m_btnOpenFile->setMaximumHeight(24);
|
||||
connect(m_btnOpenFile, &QPushButton::clicked, this, &CloudViewMainWindow::onOpenFile);
|
||||
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->setMinimumHeight(24);
|
||||
m_btnOpenPose->setMaximumHeight(24);
|
||||
@ -518,7 +523,6 @@ QGroupBox* CloudViewMainWindow::createLineGroup()
|
||||
m_btnSelectByNumber->setMaximumHeight(24);
|
||||
m_btnSelectByNumber->setMaximumWidth(50);
|
||||
connect(m_btnSelectByNumber, &QPushButton::clicked, this, &CloudViewMainWindow::onSelectLineByNumber);
|
||||
connect(m_lineNumberInput, &QLineEdit::returnPressed, this, &CloudViewMainWindow::onSelectLineByNumber);
|
||||
inputLayout->addWidget(m_lineNumberInput, 1);
|
||||
inputLayout->addWidget(m_btnSelectByNumber);
|
||||
layout->addLayout(inputLayout);
|
||||
@ -575,42 +579,17 @@ QGroupBox* CloudViewMainWindow::createCloudListGroup()
|
||||
|
||||
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(
|
||||
this,
|
||||
"打开文件",
|
||||
"打开点云文件",
|
||||
QString(),
|
||||
"所有支持格式 (*.pcd *.txt);;PCD 文件 (*.pcd);;TXT 文件 (*.txt);;所有文件 (*.*)"
|
||||
"点云文件 (*.pcd *.txt);;PCD 文件 (*.pcd);;TXT 文件 (*.txt);;所有文件 (*.*)"
|
||||
);
|
||||
|
||||
if (fileName.isEmpty()) {
|
||||
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("正在加载点云...");
|
||||
|
||||
QFileInfo fileInfo(fileName);
|
||||
@ -622,8 +601,9 @@ bool CloudViewMainWindow::loadPointCloudFile(const QString& fileName)
|
||||
int result = m_converter->loadFromFile(fileName.toStdString(), rgbCloud);
|
||||
|
||||
if (result != 0) {
|
||||
LOG_INFO("[CloudView] Load point cloud failed: %s\n", m_converter->getLastError().c_str());
|
||||
return false;
|
||||
QMessageBox::critical(this, "错误", QString("加载点云失败: %1").arg(QString::fromStdString(m_converter->getLastError())));
|
||||
statusBar()->showMessage("加载失败");
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存原始完整点云 XYZ(用于旋转/线上点等功能)
|
||||
@ -652,9 +632,11 @@ bool CloudViewMainWindow::loadPointCloudFile(const QString& fileName)
|
||||
}
|
||||
|
||||
if (hadColor) {
|
||||
// 有颜色数据:使用 addPointCloud(PointCloudXYZRGB) 显示原始颜色
|
||||
m_glWidget->addPointCloud(rgbCloud, cloudName);
|
||||
LOG_INFO("[CloudView] Loaded with original color, points: %zu\n", rgbCloud.size());
|
||||
} else {
|
||||
// 无颜色数据:使用 addPointCloud(PointCloudXYZ) 显示(颜色表轮换)
|
||||
m_glWidget->addPointCloud(m_originalCloud, cloudName);
|
||||
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);
|
||||
|
||||
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("正在加载线段...");
|
||||
|
||||
LaserDataLoader loader;
|
||||
std::vector<std::vector<SVzNLPointXYZRGBA>> polyLines;
|
||||
int result = loader.LoadPolySegments(fileName.toStdString(), polyLines);
|
||||
|
||||
if (result != 0) {
|
||||
LOG_INFO("[CloudView] Load segments failed: %s\n", loader.GetLastError().c_str());
|
||||
return false;
|
||||
QFile file(fileName);
|
||||
if (!file.open(QIODevice::ReadOnly | QIODevice::Text)) {
|
||||
QMessageBox::critical(this, "错误", QString("无法打开文件: %1").arg(fileName));
|
||||
statusBar()->showMessage("加载失败");
|
||||
return;
|
||||
}
|
||||
|
||||
if (polyLines.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 将折线点转换为线段(使用点自带的颜色)
|
||||
QVector<LineSegment> segments;
|
||||
for (const auto& polyLine : polyLines) {
|
||||
for (size_t i = 0; i + 1 < polyLine.size(); ++i) {
|
||||
const auto& p1 = polyLine[i];
|
||||
const auto& p2 = polyLine[i + 1];
|
||||
// 取起点颜色作为线段颜色,nRGB 为 (A<<24)|BGR 打包格式
|
||||
float r = ((p1.nRGB >> 0) & 0xFF) / 255.0f;
|
||||
float g = ((p1.nRGB >> 8) & 0xFF) / 255.0f;
|
||||
float b = ((p1.nRGB >> 16) & 0xFF) / 255.0f;
|
||||
uint8_t a = static_cast<uint8_t>((p1.nRGB >> 24) & 0xFF);
|
||||
// A > 1 时作为线宽使用
|
||||
float lineWidth = (a > 1) ? static_cast<float>(a) : 0.0f;
|
||||
segments.append(LineSegment(
|
||||
p1.x, p1.y, p1.z,
|
||||
p2.x, p2.y, p2.z,
|
||||
r, g, b, lineWidth));
|
||||
QTextStream in(&file);
|
||||
int lineNum = 0;
|
||||
int validCount = 0;
|
||||
|
||||
while (!in.atEnd()) {
|
||||
QString line = in.readLine().trimmed();
|
||||
lineNum++;
|
||||
|
||||
// 跳过空行和注释
|
||||
if (line.isEmpty() || line.startsWith('#')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// 解析格式:{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()) {
|
||||
return false;
|
||||
QMessageBox::warning(this, "警告", "文件中没有有效的线段数据");
|
||||
statusBar()->showMessage("加载失败");
|
||||
return;
|
||||
}
|
||||
|
||||
m_glWidget->addLineSegments(segments);
|
||||
|
||||
// 添加到列表
|
||||
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;
|
||||
statusBar()->showMessage(QString("已加载 %1 条线段").arg(validCount));
|
||||
LOG_INFO("[CloudView] Loaded %d line segments from %s\n", validCount, fileName.toStdString().c_str());
|
||||
}
|
||||
|
||||
void CloudViewMainWindow::onOpenPoseFile()
|
||||
@ -826,8 +831,6 @@ void CloudViewMainWindow::onOpenPoseFile()
|
||||
void CloudViewMainWindow::onClearAll()
|
||||
{
|
||||
m_glWidget->clearPointClouds();
|
||||
m_glWidget->clearLineSegments();
|
||||
m_glWidget->clearPosePoints();
|
||||
m_cloudList->clear();
|
||||
m_cloudCount = 0;
|
||||
m_currentLineNum = 0;
|
||||
|
||||
@ -137,15 +137,12 @@ int PointCloudConverter::loadFromTxt(const std::string& fileName, PointCloudXYZR
|
||||
for (const auto& line : rgbdData) {
|
||||
for (int i = 0; i < line.nPointCnt; ++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 g = static_cast<uint8_t>((pt.nRGB >> 8) & 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);
|
||||
totalCount++;
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@
|
||||
#include <QDebug>
|
||||
#include <QtMath>
|
||||
#include <cfloat>
|
||||
#include <map>
|
||||
#include "VrLog.h"
|
||||
|
||||
// OpenGL/GLU 头文件
|
||||
@ -197,27 +196,6 @@ void PointCloudGLWidget::paintGL()
|
||||
|
||||
glDisableClientState(GL_VERTEX_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();
|
||||
@ -318,19 +296,12 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QStr
|
||||
data.colors.reserve(cloud.size() * 3);
|
||||
data.totalLines = 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) {
|
||||
const auto& pt = cloud.points[i];
|
||||
if (!std::isfinite(pt.x) || !std::isfinite(pt.y) || !std::isfinite(pt.z)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
size_t pointIndex = data.vertices.size() / 3;
|
||||
|
||||
data.vertices.push_back(pt.x);
|
||||
data.vertices.push_back(pt.y);
|
||||
data.vertices.push_back(pt.z);
|
||||
@ -350,23 +321,6 @@ void PointCloudGLWidget::addPointCloud(const PointCloudXYZRGB& cloud, const QStr
|
||||
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;
|
||||
}
|
||||
|
||||
// 按线宽分组绘制
|
||||
// 收集所有不同的线宽值(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);
|
||||
glLineWidth(2.0f);
|
||||
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);
|
||||
glVertex3f(seg.x1, seg.y1, seg.z1);
|
||||
glVertex3f(seg.x2, seg.y2, seg.z2);
|
||||
}
|
||||
glEnd();
|
||||
}
|
||||
|
||||
glEnd();
|
||||
glLineWidth(1.0f);
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,7 @@
|
||||
#include <QIcon>
|
||||
#include "CloudViewMainWindow.h"
|
||||
|
||||
#define APP_VERSION "1.1.2"
|
||||
#define APP_VERSION "1.1.0"
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user