#include #include #include #include #include #include #include #include #include "CloudShow.h" #include #include #include #include #include // ============================================================ // 工厂方法 // ============================================================ std::unique_ptr ICloudShow::Create() { return std::make_unique(); } // ============================================================ // 构造 / 析构 // ============================================================ CCloudShow::CCloudShow() { } CCloudShow::~CCloudShow() { Stop(); } // ============================================================ // Show — 单次显示,阻塞直到关闭窗口 // ============================================================ int CCloudShow::Show(PointCloudPtr cloud, const std::string& windowName) { if (!cloud || cloud->points.empty()) { return -1; } pcl::visualization::PCLVisualizer viewer(windowName); viewer.setSize(670, 450); viewer.setBackgroundColor(0.05, 0.05, 0.05); viewer.addPointCloud(cloud, windowName); viewer.setPointCloudRenderingProperties( pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2.0, windowName); viewer.initCameraParameters(); viewer.setCameraPosition(-10000, 0, 8000, 0, 0, 0, 0, 0, 1); // 右下角坐标轴 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(); // 右侧保存按钮(viewport 归一化坐标,始终在右侧中部) 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) { if (m_bRunning) { return 0; } m_windowName = windowName; m_bFirstCloud = true; m_bQuitRequested = false; m_bSaveRequested = false; try { m_viewer = std::make_shared(windowName); m_viewer->setBackgroundColor(0.05, 0.05, 0.05); m_viewer->setSize(670, 450); m_viewer->initCameraParameters(); m_viewer->setCameraPosition(-10000, 0, 8000, 0, 0, 0, 0, 0, 1); m_viewer->registerKeyboardCallback(&CCloudShow::keyboardCallback, this); // 右下角坐标轴 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(); // 右侧保存按钮(viewport 归一化坐标,始终在右侧中部) 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 = m_viewer->getRendererCollection()->GetFirstRenderer(); if (renderer) renderer->AddActor2D(textActor); textActor->Delete(); 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]; if (rx >= 0.83f && rx <= 0.94f && ry >= 0.43f && ry <= 0.53f) m_bSaveRequested = 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; } m_bRunning = true; return 0; } // ============================================================ // UpdateCloud — 更新当前点云(线程安全,可跨线程调用) // ============================================================ int CCloudShow::UpdateCloud(PointCloudPtr cloud) { if (!m_bRunning || !cloud) { return -1; } { std::lock_guard lock(m_cloudMutex); m_currentCloud = cloud; m_bUpdated = true; } return 0; } // ============================================================ // Stop — 关闭窗口 // ============================================================ int CCloudShow::Stop() { if (!m_bRunning) { return 0; } m_bRunning = false; if (m_axesWidget) { m_axesWidget->Delete(); m_axesWidget = 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; } // ============================================================ // keyboardCallback — PCL 键盘事件 → 原子标志 // ============================================================ void CCloudShow::keyboardCallback(const pcl::visualization::KeyboardEvent& event, void* cookie) { if (!event.keyDown()) return; auto* self = static_cast(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 — 消费点云 + 驱动一次渲染(必须与 Start 同线程) // ============================================================ int CCloudShow::SpinOnce(int ms) { if (!m_bRunning || !m_viewer) { return -1; } if (m_viewer->wasStopped()) { m_bQuitRequested = true; Stop(); return -1; } PointCloudPtr cloud; { std::lock_guard lock(m_cloudMutex); if (m_bUpdated) { cloud = m_currentCloud; m_bUpdated = false; } } if (cloud && !cloud->points.empty()) { if (m_bFirstCloud) { m_viewer->addPointCloud(cloud, m_windowName); m_viewer->setPointCloudRenderingProperties( pcl::visualization::PCL_VISUALIZER_POINT_SIZE, 2.0, m_windowName); m_viewer->resetCamera(); m_viewer->setCameraPosition(-10000, 0, 8000, 0, 0, 0, 0, 0, 1); m_bFirstCloud = false; } else { m_viewer->updatePointCloud(cloud, m_windowName); } } m_viewer->spinOnce(ms); return 0; } // ============================================================ // SaveToTxt — 保存为 LaserDataLoader::DebugSaveLaser 兼容 TXT 格式 // 格式: LineNum/DataType:0/ScanSpeed:0/PointAdjust:1/MaxTimeStamp:0_0 // Line__0_ // { x, y, z } - { 0, 0 } - { 0, 0 } // ============================================================ int CCloudShow::SaveToTxt(const std::string& fileName, PointCloudPtr cloud) { if (!cloud || cloud->points.empty()) { return -1; } std::ofstream ofs(fileName); if (!ofs.is_open()) { return -2; } const auto& pts = cloud->points; const size_t N = pts.size(); const size_t height = (cloud->height > 0) ? cloud->height : 1; const size_t width = (cloud->width > 0) ? cloud->width : N; ofs << "LineNum:" << height << std::endl; ofs << "DataType: 0" << std::endl; ofs << "ScanSpeed:0" << std::endl; ofs << "PointAdjust: 1" << std::endl; ofs << "MaxTimeStamp:0_0" << std::endl; for (size_t line = 0; line < height; ++line) { size_t start = line * width; size_t end = (line + 1 == height) ? N : (start + width); size_t count = end - start; ofs << "Line_" << line << "_0_" << count << std::endl; for (size_t i = start; i < end; ++i) { float x = pts[i].x; float y = pts[i].y; float z = pts[i].z; ofs << "{ " << std::fixed << std::setprecision(3) << (std::isnan(x) ? 0.0f : x) << ", " << std::fixed << std::setprecision(3) << (std::isnan(y) ? 0.0f : y) << ", " << std::fixed << std::setprecision(3) << (std::isnan(z) ? 0.0f : z) << " } - { 0, 0 } - { 0, 0 }" << std::endl; } } ofs.close(); return 0; }