GrabBag/Module/CloudShow/Src/CloudShow.cpp

361 lines
11 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 <pcl/io/pcd_io.h>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <cmath>
// ============================================================
// 工厂方法
// ============================================================
std::unique_ptr<ICloudShow> ICloudShow::Create()
{
return std::make_unique<CCloudShow>();
}
// ============================================================
// 构造 / 析构
// ============================================================
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<pcl::PointXYZI>(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<pcl::visualization::PCLVisualizer>(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<std::mutex> 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<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 — 消费点云 + 驱动一次渲染(必须与 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<std::mutex> lock(m_cloudMutex);
if (m_bUpdated)
{
cloud = m_currentCloud;
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(-10000, 0, 8000, 0, 0, 0, 0, 0, 1);
m_bFirstCloud = false;
}
else
{
m_viewer->updatePointCloud<pcl::PointXYZI>(cloud, m_windowName);
}
}
m_viewer->spinOnce(ms);
return 0;
}
// ============================================================
// SaveToTxt — 保存为 LaserDataLoader::DebugSaveLaser 兼容 TXT 格式
// 格式: LineNum/DataType:0/ScanSpeed:0/PointAdjust:1/MaxTimeStamp:0_0
// Line_<line>_0_<count>
// { 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;
}