pcl-vtk
#pragma once
#include <QMainWindow>
#include "ui_TSPointCloudMainWindow.h"
#include <QVTKOpenGLNativeWidget.h>
// PCL/VTK 核心头文件(保留)
#include <pcl/point_cloud.h>
#include <pcl/point_types.h>
#include <pcl/io/pcd_io.h>
#include <pcl/io/ply_io.h>
#include <pcl/common/common.h>
#include <memory>
#include <mutex>
#include <vtkSmartPointer.h>
#include <vtkRenderer.h>
#include <vtkActor.h>
#include <vtkPolyData.h>
#include <vtkPoints.h>
#include <vtkCellArray.h>
#include <vtkPolyDataMapper.h>
#include <vtkAxesActor.h>
#include <vtkCamera.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkProperty.h>
#include <vtkFloatArray.h>
#include <vtkPointData.h>
#include <vtkColorTransferFunction.h>
#include <vtkInteractorStyleTrackballCamera.h> // 交互样式
#include <vtkAxesActor.h> // 坐标轴Actor头文件
#include <vtkRenderer.h>
#include <vtkActorCollection.h>
#include <vtkCaptionActor2D.h>
#include <vtkTextActor.h>
#include <vtkScalarBarActor.h>
#include <vtkColorTransferFunction.h> // 用于颜色渐变
#include <vtkScalarBarActor.h> // 用于颜色条 Actor
#include <vtkTextProperty.h> // 【报错缺失的这个】用于设置字体、颜色、大小
#include <vtkLookupTable.h> // (可选) 如果使用 LUT
#pragma execution_character_set("utf-8")
class TSPointCloudMainWindow : public QMainWindow
{
Q_OBJECT
public:
TSPointCloudMainWindow(QWidget *parent = nullptr);
~TSPointCloudMainWindow();
void setData(std::vector<double>& x, std::vector<double>& y, std::vector<double>& z);
protected:
void showEvent(QShowEvent* event) override;
void setupVTKRender();
vtkSmartPointer<vtkPolyData> convertPCLToVTK(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud);
void updatePointCloudHeightRamp(int preset, QColor minColor, QColor maxColor, QColor midColor, float minZ, float maxZ);
void resetRender();
private:
Ui::TSPointCloudMainWindowClass ui;
pcl::PointCloud<pcl::PointXYZ>::Ptr cloud;
bool isViewerInitialized;
QColor currentColor = Qt::white;
// VTK 核心对象
vtkSmartPointer<vtkRenderer> renderer;
vtkSmartPointer<vtkActor> pointActor;
vtkSmartPointer<vtkAxesActor> axesActor; // 三维轴Actor
};
#include "TSPointCloudMainWindow.h"
#include <vtkOrientationMarkerWidget.h>
#include <vtkInteractorStyleTrackballCamera.h>
TSPointCloudMainWindow::TSPointCloudMainWindow(QWidget* parent) :
QMainWindow(parent)
, cloud(new pcl::PointCloud<pcl::PointXYZ>)
, isViewerInitialized(false)
, renderer(vtkSmartPointer<vtkRenderer>::New())
, pointActor(vtkSmartPointer<vtkActor>::New())
{
ui.setupUi(this);
vtkObject::GlobalWarningDisplayOff();
}
TSPointCloudMainWindow::~TSPointCloudMainWindow()
{
}
void TSPointCloudMainWindow::setData(std::vector<double>& x, std::vector<double>& y, std::vector<double>& z)
{
cloud->clear();
int len = x.size();
for (int i = 0; i < len; i++)
{
pcl::PointXYZ point;
point.x = x[i];
point.y = y[i];
point.z = z[i];
cloud->push_back(point);
}
// 转换PCL到VTK(修正调用方式)
vtkSmartPointer<vtkPolyData> vtkCloud = convertPCLToVTK(cloud);
// 更新Mapper
vtkSmartPointer<vtkPolyDataMapper> mapper = vtkSmartPointer<vtkPolyDataMapper>::New();
mapper->SetInputData(vtkCloud);
pointActor->SetMapper(mapper);
resetRender();
}
void TSPointCloudMainWindow::resetRender()
{
if (!renderer || !cloud || cloud->empty()) {
return;
}
Eigen::Vector4f min_p, max_p;
pcl::getMinMax3D(*cloud, min_p, max_p);
double bounds[6] = {
static_cast<double>(min_p[0]), static_cast<double>(max_p[0]),
static_cast<double>(min_p[1]), static_cast<double>(max_p[1]),
static_cast<double>(min_p[2]), static_cast<double>(max_p[2])
};
double z_min = bounds[4];
double z_max = bounds[5];
if (z_max - z_min < 1e-6) {
z_min -= 1.0;
z_max += 1.0;
}
vtkPolyDataMapper* mapper = vtkPolyDataMapper::SafeDownCast(pointActor->GetMapper());
if (!mapper) {
return;
}
vtkPolyData* polyData = vtkPolyData::SafeDownCast(mapper->GetInput());
if (!polyData) {
return;
}
vtkSmartPointer<vtkFloatArray> zScalars = vtkSmartPointer<vtkFloatArray>::New();
zScalars->SetName("Elevation");
zScalars->SetNumberOfComponents(1);
zScalars->SetNumberOfTuples(static_cast<vtkIdType>(cloud->size()));
for (size_t i = 0; i < cloud->size(); ++i) {
zScalars->SetTuple1(static_cast<vtkIdType>(i), cloud->points[i].z);
}
polyData->GetPointData()->SetScalars(zScalars);
polyData->Modified();
mapper->SetColorModeToMapScalars();
mapper->SetScalarModeToUsePointData();
mapper->SetScalarRange(z_min, z_max);
mapper->ScalarVisibilityOn();
vtkLookupTable* lut = vtkLookupTable::SafeDownCast(mapper->GetLookupTable());
if (!lut) {
vtkSmartPointer<vtkLookupTable> newLut = vtkSmartPointer<vtkLookupTable>::New();
newLut->SetHueRange(0.667, 0.0);
newLut->SetSaturationRange(1.0, 1.0);
newLut->SetValueRange(1.0, 1.0);
newLut->SetNumberOfTableValues(256);
newLut->SetTableRange(z_min, z_max);
newLut->Build();
mapper->SetLookupTable(newLut);
}
else {
lut->SetTableRange(z_min, z_max);
lut->Build();
lut->Modified();
}
mapper->Modified();
mapper->Update();
double center[3] = {
(bounds[0] + bounds[1]) / 2.0,
(bounds[2] + bounds[3]) / 2.0,
(bounds[4] + bounds[5]) / 2.0
};
double x_span = bounds[1] - bounds[0];
double y_span = bounds[3] - bounds[2];
double z_span = bounds[5] - bounds[4];
if (x_span < 1e-6) x_span = 1.0;
if (y_span < 1e-6) y_span = 1.0;
if (z_span < 1e-6) z_span = 1.0;
// 以 XY 的较大跨度为参考,避免 target_span 被极端 z 放大反向污染
double target_span = std::max(x_span, y_span);
double scale_x = target_span / x_span;
double scale_y = target_span / y_span;
double scale_z = target_span / z_span;
// 防止 z_span 极小时 scale_z 爆炸
const double MAX_SCALE = 100.0;
scale_x = std::min(scale_x, MAX_SCALE);
scale_y = std::min(scale_y, MAX_SCALE);
scale_z = std::min(scale_z, MAX_SCALE);
std::cout << "x_span:" << x_span << std::endl;
std::cout << "y_span:" << y_span << std::endl;
std::cout << "z_span:" << z_span << std::endl;
std::cout << "scale_x:" << scale_x << std::endl;
std::cout << "scale_y:" << scale_y << std::endl;
std::cout << "scale_z:" << scale_z << std::endl;
pointActor->SetScale(scale_x, scale_y, scale_z);
vtkCamera* camera = renderer->GetActiveCamera();
if (camera) {
camera->ParallelProjectionOff();
camera->SetFocalPoint(center);
camera->SetViewUp(0, 0, 1);
}
renderer->ResetCamera();
if (camera) {
double scaled_x = x_span * scale_x;
double scaled_y = y_span * scale_y;
double scaled_z = z_span * scale_z;
double diag = std::sqrt(
scaled_x * scaled_x +
scaled_y * scaled_y +
scaled_z * scaled_z
);
if (diag < 1e-6) {
diag = 1.0;
}
double camDist = camera->GetDistance();
double nearClip = std::max(0.01, camDist - diag * 2.0);
double farClip = camDist + diag * 2.0;
camera->SetClippingRange(nearClip, farClip);
camera->SetViewUp(0, 0, 1);
}
ui.m_qvtkWidget->renderWindow()->Render();
ui.m_qvtkWidget->update();
updatePointCloudHeightRamp(1, QColor(), QColor(), QColor(),
static_cast<float>(z_min), static_cast<float>(z_max));
ui.m_qvtkWidget->renderWindow()->Render();
ui.m_qvtkWidget->update();
}
vtkSmartPointer<vtkPolyData> TSPointCloudMainWindow::convertPCLToVTK(const pcl::PointCloud<pcl::PointXYZ>::Ptr& cloud)
{
vtkSmartPointer<vtkPoints> points = vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkCellArray> vertices = vtkSmartPointer<vtkCellArray>::New();
// 遍历PCL点云
for (const auto& p : *cloud) {
if (std::isnan(p.x) || std::isnan(p.y) || std::isnan(p.z)) continue;
vtkIdType id = points->InsertNextPoint(p.x, p.y, p.z);
vertices->InsertNextCell(1, &id);
}
// 构建VTK PolyData
vtkSmartPointer<vtkPolyData> polyData = vtkSmartPointer<vtkPolyData>::New();
polyData->SetPoints(points);
polyData->SetVerts(vertices);
return polyData;
}
// 窗口显示事件
void TSPointCloudMainWindow::showEvent(QShowEvent* event)
{
QMainWindow::showEvent(event);
if (!isViewerInitialized) {
setupVTKRender();
isViewerInitialized = true;
}
}
void TSPointCloudMainWindow::setupVTKRender()
{
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow =
vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
ui.m_qvtkWidget->setRenderWindow(renderWindow);
renderWindow->AddRenderer(renderer);
vtkRenderWindowInteractor* interactor = ui.m_qvtkWidget->interactor();
if (interactor)
{
interactor->SetRenderWindow(renderWindow);
// 关键:启用相机交互风格
vtkSmartPointer<vtkInteractorStyleTrackballCamera> style =
vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
interactor->SetInteractorStyle(style);
interactor->Initialize();
}
renderer->SetBackground(0.0, 0.0, 0.0);
renderer->ResetCamera();
// 点云 Actor
pointActor->GetProperty()->SetPointSize(2);
pointActor->GetProperty()->SetColor(this->currentColor.redF(),
this->currentColor.greenF(),
this->currentColor.blueF());
renderer->AddActor(pointActor);
// 方向坐标轴,不再加到主 renderer
axesActor = vtkSmartPointer<vtkAxesActor>::New();
axesActor->SetTotalLength(0.8, 0.8, 0.8);
axesActor->SetAxisLabels(1);
vtkSmartPointer<vtkOrientationMarkerWidget> axesWidget =
vtkSmartPointer<vtkOrientationMarkerWidget>::New();
axesWidget->SetOrientationMarker(axesActor);
axesWidget->SetInteractor(interactor);
axesWidget->SetViewport(0.0, 0.0, 0.2, 0.2); // 左下角
axesWidget->SetEnabled(1);
axesWidget->InteractiveOff(); // 不允许鼠标单独拖它
}
//void TSPointCloudMainWindow::setupVTKRender()
//{
// vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow =
// vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
// ui.m_qvtkWidget->setRenderWindow(renderWindow);
// renderWindow->AddRenderer(renderer);
//
// // 初始化交互器
// vtkRenderWindowInteractor* interactor = ui.m_qvtkWidget->interactor();
// if (interactor)
// {
// interactor->SetRenderWindow(renderWindow);
// interactor->Initialize();
// }
//
// // 配置渲染器
// renderer->SetBackground(0.0, 0.0, 0.0); // 黑色背景
// renderer->ResetCamera();
//
// // 添加坐标系(作为类成员,默认隐藏)
// axesActor = vtkSmartPointer<vtkAxesActor>::New();
// axesActor->SetTotalLength(0.2, 0.2, 0.2);
// axesActor->SetAxisLabels(0);
// axesActor->SetVisibility(true); // 关键:设置为隐藏
// renderer->AddActor(axesActor);
//
// // 初始化点云Actor
// pointActor->GetProperty()->SetPointSize(2);
// pointActor->GetProperty()->SetColor(this->currentColor.redF(),
// this->currentColor.greenF(),
// this->currentColor.blueF());
// renderer->AddActor(pointActor);
//}
void TSPointCloudMainWindow::updatePointCloudHeightRamp(int preset, QColor minColor, QColor maxColor, QColor midColor, float minZ, float maxZ)
{
// ========== 1. 组件校验 ==========
if (!pointActor || !renderer) return;
vtkPolyDataMapper* mapper = vtkPolyDataMapper::SafeDownCast(pointActor->GetMapper());
if (!mapper || !cloud || cloud->empty()) return;
vtkPolyData* polyData = mapper->GetInput();
if (!polyData) return;
// ========== 2. 高度范围校验 ==========
if (qFuzzyCompare(minZ, maxZ) || minZ > maxZ) {
Eigen::Vector4f min_p, max_p;
pcl::getMinMax3D(*cloud, min_p, max_p);
minZ = static_cast<float>(min_p[2]);
maxZ = static_cast<float>(max_p[2]);
// 防止范围为 0
if (qFuzzyCompare(minZ, maxZ)) {
minZ -= 0.1f;
maxZ += 0.1f;
}
}
// ========== 3. 确保 Z 轴标量存在 ==========
vtkPointData* pointData = polyData->GetPointData();
// 检查是否已有名为 "ZCoordinates" 的标量,避免重复创建
bool needCreateScalars = true;
if (pointData && pointData->GetScalars()) {
if (pointData->GetScalars()->GetName() && strcmp(pointData->GetScalars()->GetName(), "ZCoordinates") == 0) {
needCreateScalars = false;
}
}
if (needCreateScalars) {
vtkSmartPointer<vtkFloatArray> zScalars = vtkSmartPointer<vtkFloatArray>::New();
zScalars->SetName("ZCoordinates");
zScalars->SetNumberOfComponents(1);
zScalars->SetNumberOfTuples(polyData->GetNumberOfPoints());
double point[3];
for (vtkIdType i = 0; i < polyData->GetNumberOfPoints(); ++i) {
polyData->GetPoint(i, point);
zScalars->SetValue(i, point[2]);
}
pointData->SetScalars(zScalars);
polyData->Modified(); // 通知数据已修改
}
vtkSmartPointer<vtkColorTransferFunction> colorTF =
vtkSmartPointer<vtkColorTransferFunction>::New();
colorTF->SetColorSpaceToRGB();
auto mapPosToZ = [minZ, maxZ](double pos) -> double
{
return minZ + pos * (maxZ - minZ);
};
colorTF->AddRGBPoint(mapPosToZ(0.00), 0.0, 0.0, 139.0 / 255.0); // #00008B
colorTF->AddRGBPoint(mapPosToZ(0.17), 0.0, 0.0, 1.0); // #0000FF
colorTF->AddRGBPoint(mapPosToZ(0.34), 0.0, 1.0, 1.0); // #00FFFF
colorTF->AddRGBPoint(mapPosToZ(0.50), 0.0, 1.0, 0.0); // #00FF00
colorTF->AddRGBPoint(mapPosToZ(0.67), 1.0, 1.0, 0.0); // #FFFF00
colorTF->AddRGBPoint(mapPosToZ(0.84), 1.0, 0.0, 0.0); // #FF0000
colorTF->AddRGBPoint(mapPosToZ(1.00), 139.0 / 255.0, 0.0, 0.0); // #8B0000
mapper->ScalarVisibilityOn();
mapper->SetScalarModeToUsePointData();
mapper->SetLookupTable(colorTF); // 直接应用颜色传输函数
mapper->SetScalarRange(minZ, maxZ);
mapper->SetUseLookupTableScalarRange(true);
mapper->Update();
// 重置 Actor 颜色,防止覆盖渐变
pointActor->GetProperty()->SetColor(1.0, 1.0, 1.0);
pointActor->GetProperty()->SetOpacity(1.0);
// 假设你没有成员变量,我们采用“查找并移除旧条,创建新条”的策略,防止重复叠加
vtkActorCollection* actors = renderer->GetActors();
actors->InitTraversal();
vtkActor* oldBar = nullptr;
while (vtkActor* actor = actors->GetNextActor()) {
if (actor->IsA("vtkScalarBarActor")) {
oldBar = actor;
break;
}
}
if (oldBar) {
renderer->RemoveActor(oldBar);
}
// 6.2 创建新的 ScalarBarActor
vtkSmartPointer<vtkScalarBarActor> scalarBar = vtkSmartPointer<vtkScalarBarActor>::New();
// 关联 LookupTable (必须与 Mapper 使用的相同)
scalarBar->SetLookupTable(mapper->GetLookupTable());
//// 设置标题
//scalarBar->SetTitle("Height (Z)");
//scalarBar->GetTitleTextProperty()->SetColor(1.0, 1.0, 1.0); // 标题颜色 (白色)
//scalarBar->GetTitleTextProperty()->SetFontSize(14);
// 设置标签格式 (例如保留 2 位小数)
scalarBar->SetLabelFormat("%.4f");
scalarBar->GetLabelTextProperty()->SetColor(1.0, 1.0, 1.0); // 标签颜色
scalarBar->GetLabelTextProperty()->SetFontSize(12);
// 设置位置和大小 (Viewport 坐标 0.0-1.0)
scalarBar->SetWidth(0.1);
scalarBar->SetHeight(0.8);
scalarBar->SetPosition(0.85, 0.1); // x, y (左下角坐标)
// 设置标签数量
scalarBar->SetNumberOfLabels(6);
// 6.3 添加到渲染器
renderer->AddActor(scalarBar);
// 强制重绘
ui.m_qvtkWidget->renderWindow()->Render();
ui.m_qvtkWidget->update();
QCoreApplication::processEvents();
}
