GrabBag/Module/CloudShow/Src/CloudShow.cpp
2026-06-13 13:24:31 +08:00

920 lines
31 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#include <vtkAxesActor.h>
#include <vtkOrientationMarkerWidget.h>
#include <vtkRenderWindow.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkTextActor.h>
#include <vtkTextProperty.h>
#include <vtkCoordinate.h>
#include <vtkRendererCollection.h>
#include "CloudShow.h"
#include "LaserDataLoader.h"
#include <pcl/io/pcd_io.h>
#include <chrono>
#include <cmath>
#include <cstdio>
#include <ctime>
#include <exception>
#include <filesystem>
#include <iostream>
#include <new>
#include <sstream>
// ============================================================
// 工厂方法
// ============================================================
std::unique_ptr<ICloudShow> ICloudShow::Create()
{
return std::make_unique<CCloudShow>();
}
CCloudShow::CCloudShow() = default;
CCloudShow::~CCloudShow()
{
Stop();
}
// ============================================================
// 内部:根据数据类型返回单点字节数
// ============================================================
static size_t pointSize(EVzResultDataType t)
{
switch (t)
{
case keResultDataType_PointXYZI: return sizeof(SVzNLPointXYZI);
case keResultDataType_PointXYZRGBA: return sizeof(SVzNLPointXYZRGBA);
case keResultDataType_PointF: return sizeof(SVzNL3DPointF);
case keResultDataType_Position: return sizeof(SVzNL3DPosition);
default: return 0;
}
}
// ============================================================
// 内部CloudData → PCL仅处理 PointXYZI 类型,其余跳过)
// ============================================================
static pcl::PointCloud<pcl::PointXYZI>::Ptr toPcl(const ICloudShow::CloudData& cloudData)
{
size_t total = 0;
for (const auto& p : cloudData)
{
if (p.first == keResultDataType_PointXYZI && p.second.p3DPoint)
total += p.second.nPointCount;
}
if (total == 0)
return nullptr;
auto cloud = std::make_shared<pcl::PointCloud<pcl::PointXYZI>>();
cloud->is_dense = true;
cloud->points.resize(total); // 一次分配,下标赋值,避免 40 万次 push_back
size_t idx = 0;
for (const auto& p : cloudData)
{
if (p.first != keResultDataType_PointXYZI || !p.second.p3DPoint)
continue;
const auto* src = static_cast<const SVzNLPointXYZI*>(p.second.p3DPoint);
for (int i = 0; i < p.second.nPointCount; ++i)
{
cloud->points[idx].x = src[i].fData[0];
cloud->points[idx].y = src[i].fData[1];
cloud->points[idx].z = src[i].fData[2];
cloud->points[idx].intensity = src[i].fData_c[0];
++idx;
}
}
cloud->width = static_cast<uint32_t>(idx);
cloud->height = 1;
return cloud;
}
// ============================================================
// Show — 单次显示,阻塞直到关闭窗口
// ============================================================
int CCloudShow::Show(const CloudData& cloudData,
const CloudFrameInfo& info,
const std::string& windowName)
{
auto pclCloud = toPcl(cloudData);
if (!pclCloud)
return -1;
pcl::visualization::PCLVisualizer viewer(windowName);
viewer.setSize(670, 450);
viewer.setBackgroundColor(0.05, 0.05, 0.05);
viewer.addPointCloud<pcl::PointXYZI>(pclCloud, windowName);
viewer.setPointCloudRenderingProperties(
pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2.0, windowName);
viewer.initCameraParameters();
viewer.setCameraPosition(0, -100000, -50000, 0, 0, 50000, 0, -1, 0);
// 右下角坐标轴
vtkAxesActor* axes = vtkAxesActor::New();
vtkOrientationMarkerWidget* widget = vtkOrientationMarkerWidget::New();
widget->SetOrientationMarker(axes);
vtkRenderWindowInteractor* iren = viewer.getRenderWindow()->GetInteractor();
if (!iren)
{
iren = vtkRenderWindowInteractor::New();
viewer.getRenderWindow()->SetInteractor(iren);
iren->Delete();
}
widget->SetInteractor(iren);
widget->SetViewport(0.85, 0.0, 1.0, 0.15);
widget->SetEnabled(1);
widget->InteractiveOff();
// SAVE 按钮
vtkTextActor* textActor = vtkTextActor::New();
textActor->SetInput("SAVE");
textActor->GetTextProperty()->SetColor(0.2, 0.8, 0.2);
textActor->GetTextProperty()->SetFontSize(20);
textActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
textActor->SetPosition(0.88, 0.48);
vtkRenderer* renderer = viewer.getRendererCollection()->GetFirstRenderer();
if (renderer)
renderer->AddActor2D(textActor);
textActor->Delete();
viewer.registerMouseCallback([&viewer, this](const pcl::visualization::MouseEvent& event) {
if (event.getType() != pcl::visualization::MouseEvent::MouseButtonPress)
return;
if (event.getButton() != pcl::visualization::MouseEvent::LeftButton)
return;
int* sz = viewer.getRenderWindow()->GetSize();
if (sz[0] <= 0 || sz[1] <= 0)
return;
float rx = (float)event.getX() / sz[0];
float ry = (float)event.getY() / sz[1];
if (rx >= 0.83f && rx <= 0.94f && ry >= 0.43f && ry <= 0.53f)
m_bSaveRequested = true;
});
viewer.spin();
widget->Delete();
axes->Delete();
return 0;
}
// ============================================================
// Start — 主线程创建 viewer注册键盘/鼠标回调
// ============================================================
int CCloudShow::Start(const std::string& windowName, void* hwnd)
{
if (m_bRunning)
return 0;
m_windowName = windowName;
m_bFirstCloud = true;
m_bQuitRequested = false;
m_bSaveRequested = false;
try
{
m_viewer = std::make_shared<pcl::visualization::PCLVisualizer>(windowName);
m_viewer->setBackgroundColor(0.05, 0.05, 0.05);
m_viewer->setSize(670, 450);
m_viewer->initCameraParameters();
m_viewer->setCameraPosition(0, -100000, -50000, 0, 0, 50000, 0, -1, 0);
m_viewer->registerKeyboardCallback(&CCloudShow::keyboardCallback, this);
#ifdef _WIN32
if (hwnd)
m_viewer->getRenderWindow()->SetParentId(hwnd);
#endif
// 右下角坐标轴
vtkAxesActor* axes = vtkAxesActor::New();
m_axesWidget = vtkOrientationMarkerWidget::New();
m_axesWidget->SetOrientationMarker(axes);
vtkRenderWindowInteractor* iren = m_viewer->getRenderWindow()->GetInteractor();
if (!iren)
{
iren = vtkRenderWindowInteractor::New();
m_viewer->getRenderWindow()->SetInteractor(iren);
iren->Delete();
}
m_axesWidget->SetInteractor(iren);
m_axesWidget->SetViewport(0.85, 0.0, 1.0, 0.15);
m_axesWidget->SetEnabled(1);
m_axesWidget->InteractiveOff();
axes->Delete();
vtkRenderer* renderer = m_viewer->getRendererCollection()->GetFirstRenderer();
// SAVE 按钮
vtkTextActor* saveActor = vtkTextActor::New();
saveActor->SetInput("SAVE");
saveActor->GetTextProperty()->SetColor(0.2, 0.8, 0.2);
saveActor->GetTextProperty()->SetFontSize(20);
saveActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
saveActor->SetPosition(0.88, 0.45);
if (renderer)
renderer->AddActor2D(saveActor);
saveActor->Delete();
// START/STOP 按钮
m_pStartStopBtnActor = vtkTextActor::New();
m_pStartStopBtnActor->SetInput("START");
m_pStartStopBtnActor->GetTextProperty()->SetColor(0.2, 0.8, 0.2);
m_pStartStopBtnActor->GetTextProperty()->SetFontSize(20);
m_pStartStopBtnActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
m_pStartStopBtnActor->SetPosition(0.87, 0.35);
if (renderer)
renderer->AddActor2D(m_pStartStopBtnActor);
// 保存进度文字
m_pProgressActor = vtkTextActor::New();
m_pProgressActor->SetInput("");
m_pProgressActor->GetTextProperty()->SetColor(0.8, 0.8, 0.2);
m_pProgressActor->GetTextProperty()->SetFontSize(14);
m_pProgressActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
m_pProgressActor->SetPosition(0.85, 0.25);
if (renderer)
renderer->AddActor2D(m_pProgressActor);
m_viewer->registerMouseCallback([this](const pcl::visualization::MouseEvent& event) {
if (event.getType() != pcl::visualization::MouseEvent::MouseButtonPress)
return;
if (event.getButton() != pcl::visualization::MouseEvent::LeftButton)
return;
int* sz = m_viewer->getRenderWindow()->GetSize();
if (sz[0] <= 0 || sz[1] <= 0)
return;
float rx = (float)event.getX() / sz[0];
float ry = (float)event.getY() / sz[1];
// SAVE 按钮区域
if (rx >= 0.83f && rx <= 0.94f && ry >= 0.40f && ry <= 0.50f)
m_bSaveRequested = true;
// START/STOP 按钮区域
else if (rx >= 0.83f && rx <= 0.94f && ry >= 0.30f && ry <= 0.40f)
m_bStartStopRequested = true;
});
}
catch (const std::exception& e)
{
std::cerr << "[CloudShow] 创建 PCL 窗口失败: " << e.what() << std::endl;
return -1;
}
catch (...)
{
std::cerr << "[CloudShow] 创建 PCL 窗口失败: 未知异常" << std::endl;
return -1;
}
// 预分配复用缓冲区,避免热路径 realloc 导致卡顿
{
// 显示缓冲:~500k 点 × 16B = 8MB
m_renderPool = std::make_shared<pcl::PointCloud<pcl::PointXYZI>>();
m_renderPool->points.reserve(500000);
// S 键缓存缓冲600 线 × 1500 点 × 32B ≈ 29MB
m_lastCloudBuffers.resize(600);
for (auto& buf : m_lastCloudBuffers)
buf.reserve(1500 * sizeof(SVzNLPointXYZI));
}
m_bRunning = true;
return 0;
}
// ============================================================
// PushFrame — 更新显示 + 入保存队列
// ============================================================
void CCloudShow::PushFrame(const CloudData& cloudData, const CloudFrameInfo& info)
{
if (!m_bRunning)
return;
try
{
// 保存进行中m_saveTotal > 0 表示 STOP 已点击saveLoop 正在写盘)
// 此时跳过显示更新和 S 键缓存,避免与写盘争抢内存/IO 导致 UI 卡死
const bool bSaving = (m_saveTotal.load() > 0);
if (!bSaving)
{
// 仅非连续保存时缓存最近一帧 CloudData 的深拷贝(供 S 键单帧保存)
if (!m_bSaveActive.load())
{
// 1. 锁内取出旧数据swap 只交换指针,极快)
CloudData oldData;
std::vector<std::vector<uint8_t>> oldBuffers;
{
std::lock_guard<std::mutex> lk(m_lastCloudMutex);
m_lastFrameInfo = info;
oldData.swap(m_lastCloudData);
oldBuffers.swap(m_lastCloudBuffers);
}
// 2. 锁外:复用 oldBuffersresize + memcpy零分配
oldBuffers.resize(cloudData.size());
oldData.clear();
oldData.reserve(cloudData.size());
for (size_t i = 0; i < cloudData.size(); ++i)
{
const auto& src = cloudData[i];
oldData.push_back(src);
auto& ld = oldData.back().second;
ld.p2DPoint = nullptr;
if (ld.p3DPoint && ld.nPointCount > 0)
{
size_t sz = pointSize(src.first) * static_cast<size_t>(ld.nPointCount);
if (sz > 0)
{
// 加 4KB margin避免点数的帧间微小波动导致反复 realloc
if (oldBuffers[i].capacity() < sz)
oldBuffers[i].reserve(sz + 4096);
oldBuffers[i].resize(sz);
std::memcpy(oldBuffers[i].data(), ld.p3DPoint, sz);
ld.p3DPoint = oldBuffers[i].data();
}
else { ld.p3DPoint = nullptr; }
}
}
// 3. 锁内写入新数据swap 只交换指针,极快)
{
std::lock_guard<std::mutex> lk(m_lastCloudMutex);
m_lastCloudData.swap(oldData);
m_lastCloudBuffers.swap(oldBuffers);
}
}
// 显示:复用 m_renderPoolresize + 下标赋值(零分配)
{
size_t total = 0;
for (const auto& p : cloudData)
if (p.first == keResultDataType_PointXYZI && p.second.p3DPoint)
total += p.second.nPointCount;
if (total > 0)
{
if (!m_renderPool)
m_renderPool = std::make_shared<pcl::PointCloud<pcl::PointXYZI>>();
auto& cloud = *m_renderPool;
cloud.is_dense = true;
if (cloud.points.capacity() < total)
cloud.points.reserve(total + 2048);
cloud.points.resize(total);
size_t idx = 0;
for (const auto& p : cloudData)
{
if (p.first != keResultDataType_PointXYZI || !p.second.p3DPoint)
continue;
const auto* src = static_cast<const SVzNLPointXYZI*>(p.second.p3DPoint);
for (int i = 0; i < p.second.nPointCount; ++i)
{
cloud.points[idx].x = src[i].fData[0];
cloud.points[idx].y = src[i].fData[1];
cloud.points[idx].z = src[i].fData[2];
cloud.points[idx].intensity = src[i].fData_c[0];
++idx;
}
}
cloud.width = static_cast<uint32_t>(idx);
cloud.height = 1;
std::lock_guard<std::mutex> lk(m_cloudMutex);
m_displayCloud.swap(m_renderPool); // 旧 displayCloud 变成新的 renderPool
m_bUpdated = true;
}
}
}
// 保存按帧间隔缓存默认每10帧存1帧
// 注意m_nPushFrameCount 仅回调线程独占写入,无需原子操作
m_nPushFrameCount++;
if (m_bSaveActive.load() && (m_nPushFrameCount % m_nSaveFrameInterval == 0))
{
SaveTask task = acquireTask(); // 从池取预分配 task
packIntoTask(task, cloudData, info); // memcpy 到预分配 buffer
{
std::lock_guard<std::mutex> lk(m_saveMutex);
if (m_bSaveActive.load()) // 双重检查STOP 可能在上一步期间触发
{
if (!task.cloudData.empty())
m_saveQueue.emplace_back(std::move(task));
else
releaseTask(task);
m_saveCv.notify_one();
}
else
{
releaseTask(task); // STOP 已点击,丢弃此帧
}
}
}
}
catch (const std::bad_alloc&)
{
m_nSaveDropped.fetch_add(1, std::memory_order_relaxed);
}
catch (const std::exception&)
{
m_nSaveDropped.fetch_add(1, std::memory_order_relaxed);
}
catch (...)
{
m_nSaveDropped.fetch_add(1, std::memory_order_relaxed);
}
}
// ============================================================
// Stop — 关闭窗口,停止保存线程
// ============================================================
int CCloudShow::Stop()
{
if (!m_bRunning)
return 0;
m_bRunning = false;
m_bSaveActive = false;
m_bFlushAndSave = false;
m_saveExit = true;
m_saveCv.notify_all();
for (auto& t : m_saveThreads)
{
if (t.joinable())
t.join();
}
m_saveThreads.clear();
// 释放缓冲池 + 保存队列残余内存
{
std::lock_guard<std::mutex> lk(m_taskPoolMutex);
m_taskPool.clear();
}
{
std::lock_guard<std::mutex> lk(m_saveMutex);
m_saveQueue.clear();
}
if (m_axesWidget)
{
m_axesWidget->Delete();
m_axesWidget = nullptr;
}
if (m_pStartStopBtnActor)
{
m_pStartStopBtnActor->Delete();
m_pStartStopBtnActor = nullptr;
}
if (m_pProgressActor)
{
m_pProgressActor->Delete();
m_pProgressActor = nullptr;
}
if (m_viewer)
{
m_viewer->close();
m_viewer.reset();
}
return 0;
}
bool CCloudShow::IsRunning() const
{
return m_bRunning;
}
bool CCloudShow::IsQuitRequested() const
{
return m_bQuitRequested;
}
bool CCloudShow::IsSaveRequested() const
{
return m_bSaveRequested;
}
void CCloudShow::ClearSaveRequest()
{
m_bSaveRequested = false;
}
bool CCloudShow::IsSaveActive() const
{
return m_bSaveActive.load();
}
void CCloudShow::SetSaveBaseDir(const std::string& dir)
{
m_saveBaseDir = dir;
// 扫描已有 LaserData_*.txt从最大序号 +1 开始
int maxSeq = 0;
std::error_code ec;
for (const auto& entry : std::filesystem::directory_iterator(m_saveBaseDir, ec))
{
if (ec) break;
std::string name = entry.path().filename().string();
int n = 0;
if (sscanf(name.c_str(), "LaserData_%d.txt", &n) == 1)
{
if (n > maxSeq) maxSeq = n;
}
}
m_nSingleSaveSeq = maxSeq;
m_nSaveSeq = maxSeq;
std::cout << "[CloudShow] 保存基目录: " << m_saveBaseDir
<< " 已有最大序号: " << maxSeq << std::endl;
}
void CCloudShow::SetSaveFrameInterval(int interval)
{
if (interval < 1) interval = 1;
m_nSaveFrameInterval = interval;
std::cout << "[CloudShow] 保存帧间隔: " << interval << std::endl;
}
// ============================================================
// 内部START/STOP 切换
// ============================================================
void CCloudShow::handleStartStopToggle()
{
if (m_saveTotal.load() > 0 && !m_bSaveActive.load())
return; // 刷新中,忽略
if (!m_bSaveActive.load())
{
// === START开始缓存数据到内存不写盘 ===
m_nSaveSeq = 0; // 每次会话从 1 开始
m_saveCompleted = 0;
m_saveTotal = 0;
m_bFlushAndSave = false; // 保存线程等待,不写盘
auto now = std::time(nullptr);
std::ostringstream dirOss;
dirOss << m_saveBaseDir << "/"
<< std::put_time(std::localtime(&now), "%Y%m%d_%H%M%S");
m_currentSessionDir = dirOss.str();
std::error_code ec;
std::filesystem::create_directories(m_currentSessionDir, ec);
if (ec)
{
std::cerr << "[CloudShow] 创建目录失败: "
<< m_currentSessionDir << std::endl;
return;
}
if (m_saveThreads.empty())
{
m_saveExit = false;
for (int i = 0; i < SAVE_THREAD_COUNT; ++i)
m_saveThreads.emplace_back(&CCloudShow::saveLoop, this);
}
m_bSaveActive = true;
updateStartStopButton();
updateProgressText();
std::cout << "[CloudShow] 开始缓存 -> "
<< m_currentSessionDir << std::endl;
}
else
{
// === STOP停止缓存2线程写盘 ===
m_bSaveActive = false;
{
std::lock_guard<std::mutex> lk(m_saveMutex);
m_saveTotal = m_saveCompleted.load()
+ static_cast<int>(m_saveQueue.size());
}
m_bFlushAndSave = true; // 通知保存线程开始写盘
m_saveCv.notify_all(); // 唤醒 2 个保存线程
updateStartStopButton();
std::cout << "[CloudShow] 停止缓存,写盘 "
<< m_saveTotal.load() << " 帧..." << std::endl;
}
}
void CCloudShow::updateStartStopButton()
{
if (!m_pStartStopBtnActor)
return;
std::lock_guard<std::mutex> lk(m_textMutex);
if (m_bSaveActive.load())
{
m_pStartStopBtnActor->SetInput("STOP");
m_pStartStopBtnActor->GetTextProperty()->SetColor(1.0, 0.3, 0.3);
}
else
{
m_pStartStopBtnActor->SetInput("START");
m_pStartStopBtnActor->GetTextProperty()->SetColor(0.2, 0.8, 0.2);
}
m_pStartStopBtnActor->Modified();
}
void CCloudShow::updateProgressText()
{
if (!m_pProgressActor)
return;
std::lock_guard<std::mutex> lk(m_textMutex);
int done = m_saveCompleted.load();
int total = m_saveTotal.load();
int dropped = m_nSaveDropped.load();
std::ostringstream oss;
if (total > 0)
oss << "Saved: " << done << "/" << total;
else if (done > 0)
oss << "Saved: " << done;
{
std::lock_guard<std::mutex> lk2(m_saveMutex);
if (!m_saveQueue.empty())
oss << " Q:" << m_saveQueue.size();
}
if (dropped > 0)
oss << " drop:" << dropped;
m_pProgressActor->SetInput(oss.str().c_str());
m_pProgressActor->Modified();
}
// ============================================================
// 内部:保存线程(多线程共享队列)
// ============================================================
void CCloudShow::saveLoop()
{
while (!m_saveExit.load())
{
SaveTask task; // 在 try 外声明catch 块可访问
bool haveTask = false;
try
{
{
std::unique_lock<std::mutex> lk(m_saveMutex);
// 等待条件:退出 或 (STOP已点击 且 队列非空)
m_saveCv.wait(lk, [this] {
return m_saveExit.load()
|| (m_bFlushAndSave.load() && !m_saveQueue.empty());
});
if (m_saveQueue.empty() || !m_bFlushAndSave.load())
{
if (m_saveExit.load())
return;
continue; // 虚假唤醒或 START 期间,继续等待
}
task = std::move(m_saveQueue.front());
m_saveQueue.pop_front();
haveTask = true;
}
const int seq = ++m_nSaveSeq;
std::ostringstream ofn;
ofn << m_currentSessionDir << "/LaserData_" << seq << ".txt";
// 统计总点数
size_t totalPts = 0;
for (const auto& p : task.cloudData)
totalPts += p.second.nPointCount;
auto t0 = std::chrono::steady_clock::now();
LaserDataLoader loader;
loader.SaveLaserScanData(ofn.str(), task.cloudData,
static_cast<int>(task.info.height), 0.0f, 0, 0);
auto t1 = std::chrono::steady_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(t1 - t0).count();
std::cout << "[CloudShow] 保存 " << ofn.str()
<< " 行=" << task.cloudData.size()
<< " 点=" << totalPts
<< " 耗时=" << ms << "ms"
<< " Q=" << m_saveQueue.size()
<< std::endl;
m_saveCompleted.fetch_add(1, std::memory_order_relaxed);
// 归还 task 到缓冲池
releaseTask(task);
haveTask = false;
}
catch (const std::exception& e)
{
std::cerr << "[CloudShow] 保存异常: " << e.what()
<< " Q=" << m_saveQueue.size() << std::endl;
m_nSaveDropped.fetch_add(1, std::memory_order_relaxed);
if (haveTask) releaseTask(task);
}
catch (...)
{
std::cerr << "[CloudShow] 保存未知异常 Q="
<< m_saveQueue.size() << std::endl;
m_nSaveDropped.fetch_add(1, std::memory_order_relaxed);
if (haveTask) releaseTask(task);
}
}
}
// ============================================================
// SaveTask 缓冲池 — 消除回调线程的堆分配
// ============================================================
CCloudShow::SaveTask CCloudShow::acquireTask()
{
std::lock_guard<std::mutex> lk(m_taskPoolMutex);
if (!m_taskPool.empty())
{
SaveTask task = std::move(m_taskPool.back());
m_taskPool.pop_back();
return task;
}
return SaveTask{}; // 池空:首次/扩容时分配(稀有路径)
}
void CCloudShow::releaseTask(SaveTask& task)
{
// 清空元数据但保留 buffers 的 capacity 供下次复用
task.cloudData.clear();
task.info = CloudFrameInfo{};
std::lock_guard<std::mutex> lk(m_taskPoolMutex);
m_taskPool.push_back(std::move(task));
}
void CCloudShow::packIntoTask(SaveTask& task,
const ICloudShow::CloudData& cloudData,
const CloudFrameInfo& info)
{
task.info = info;
task.cloudData.clear();
task.cloudData.reserve(cloudData.size());
// 确保 buffers 足够(仅首次或帧结构变化时 resize
if (task.buffers.size() < cloudData.size())
task.buffers.resize(cloudData.size());
for (size_t i = 0; i < cloudData.size(); ++i)
{
const auto& src = cloudData[i];
task.cloudData.push_back(src);
auto& ld = task.cloudData.back().second;
ld.p2DPoint = nullptr;
if (ld.p3DPoint && ld.nPointCount > 0)
{
size_t sz = pointSize(src.first) * static_cast<size_t>(ld.nPointCount);
if (sz > 0)
{
if (task.buffers[i].capacity() < sz)
task.buffers[i].reserve(sz + 4096);
task.buffers[i].resize(sz);
std::memcpy(task.buffers[i].data(), ld.p3DPoint, sz);
ld.p3DPoint = task.buffers[i].data();
}
else
{
ld.p3DPoint = nullptr;
}
}
}
task.cloudData.resize(cloudData.size());
}
// ============================================================
// keyboardCallback — PCL 键盘事件 → 原子标志
// ============================================================
void CCloudShow::keyboardCallback(
const pcl::visualization::KeyboardEvent& event, void* cookie)
{
if (!event.keyDown())
return;
auto* self = static_cast<CCloudShow*>(cookie);
if (!self)
return;
std::string key = event.getKeySym();
if (key == "q" || key == "Q")
self->m_bQuitRequested = true;
else if (key == "s" || key == "S")
self->m_bSaveRequested = true;
}
// ============================================================
// SpinOnce — 消费点云 + 驱动渲染 + 保存状态维护
// ============================================================
int CCloudShow::SpinOnce(int ms)
{
if (!m_bRunning || !m_viewer)
return -1;
if (m_viewer->wasStopped())
{
m_bQuitRequested = true;
Stop();
return -1;
}
// START/STOP 按钮处理
if (m_bStartStopRequested.exchange(false))
handleStartStopToggle();
// S 键单帧保存(需持有 m_lastCloudMutex 与 PushFrame 互斥)
if (m_bSaveRequested.exchange(false))
{
// 锁内取走数据所有权swap极快避免持锁做文件 IO
CloudData localData;
std::vector<std::vector<uint8_t>> localBuffers;
CloudFrameInfo localInfo{};
{
std::lock_guard<std::mutex> lk(m_lastCloudMutex);
if (!m_lastCloudData.empty())
{
localData.swap(m_lastCloudData);
localBuffers.swap(m_lastCloudBuffers);
localInfo = m_lastFrameInfo;
}
}
// 锁外:执行文件写入(不阻塞 PushFrame
if (!localData.empty())
{
const int seq = ++m_nSingleSaveSeq;
std::ostringstream oss;
oss << m_saveBaseDir << "/LaserData_" << seq << ".txt";
LaserDataLoader loader;
loader.SaveLaserScanData(oss.str(), localData,
static_cast<int>(localInfo.height), 0.0f, 0, 0);
std::cout << "[CloudShow] 单帧保存: " << oss.str() << std::endl;
}
}
// 实时进度更新
if (m_bSaveActive.load() && m_saveCompleted.load() > 0)
{
updateProgressText();
}
else if (!m_bSaveActive.load() && m_saveTotal.load() > 0)
{
int done = m_saveCompleted.load();
int total = m_saveTotal.load();
updateProgressText();
if (done >= total)
{
std::cout << "[CloudShow] 保存完成: " << m_currentSessionDir
<< " | 共 " << total << "" << std::endl;
m_saveTotal = 0;
m_saveCompleted = 0;
m_bFlushAndSave = false; // 写盘结束,保存线程回到等待
updateProgressText();
// 释放全部内存:缓冲池 + 保存队列 + S键缓存
{
std::lock_guard<std::mutex> lk(m_taskPoolMutex);
m_taskPool.clear();
}
{
std::lock_guard<std::mutex> lk(m_saveMutex);
m_saveQueue.clear();
}
{
std::lock_guard<std::mutex> lk(m_lastCloudMutex);
m_lastCloudData.clear();
m_lastCloudBuffers.clear();
m_lastCloudBuffers.shrink_to_fit();
}
}
}
// 渲染最新点云
pcl::PointCloud<pcl::PointXYZI>::Ptr cloud;
{
std::lock_guard<std::mutex> lk(m_cloudMutex);
if (m_bUpdated)
{
cloud = m_displayCloud;
m_bUpdated = false;
}
}
if (cloud && !cloud->points.empty())
{
if (m_bFirstCloud)
{
m_viewer->addPointCloud<pcl::PointXYZI>(cloud, m_windowName);
m_viewer->setPointCloudRenderingProperties(
pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2.0, m_windowName);
m_viewer->resetCamera();
m_viewer->setCameraPosition(0, -100000, -50000, 0, 0, 50000, 0, -1, 0);
m_bFirstCloud = false;
}
else
{
m_viewer->updatePointCloud<pcl::PointXYZI>(cloud, m_windowName);
}
}
m_viewer->spinOnce(ms);
return 0;
}