I am using Qt and VTK to select cells in my VTU file. However, the highlighted cells do not match the cells in the area picker. I tried converting the world coordinates of the selected cells to screen coordinates, but the bounding box of these screen coordinates differs from the bounding box of my area picker. My code is as follows.
#include <QApplication>
#include <QPushButton>
#include <QVBoxLayout>
#include <QWidget>
#include <QVTKOpenGLNativeWidget.h>
#include <QFileDialog>
#include <vtkActor.h>
#include <vtkDataSetMapper.h>
#include <vtkGenericOpenGLRenderWindow.h>
#include <vtkInteractorStyleRubberBand3D.h>
#include <vtkNew.h>
#include <vtkPolyData.h>
#include <vtkProperty.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <vtkSmartPointer.h>
#include <vtkUnstructuredGrid.h>
#include <vtkXMLUnstructuredGridReader.h>
#include <vtkHardwareSelector.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkCallbackCommand.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <qdebug.h>
#include <qpainter.h>
#include <qpen.h>
#include <vtkCamera.h>
#include <vtkCell.h>
#include <vtkCoordinate.h>
#include <vtkMath.h>
class CustomInteractorStyle : public vtkInteractorStyleRubberBand3D
{
public:
static CustomInteractorStyle* New();
vtkTypeMacro(CustomInteractorStyle, vtkInteractorStyleRubberBand3D);
virtual void OnLeftButtonDown() override
{
// 记录起始点
StartPosition[0] = this->Interactor->GetEventPosition()[0];
StartPosition[1] = this->Interactor->GetEventPosition()[1];
vtkInteractorStyleRubberBand3D::OnLeftButtonDown();
}
virtual void OnMouseMove() override
{
if (this->State == VTKIS_ROTATE)
{
// 更新选择框
EndPosition[0] = this->Interactor->GetEventPosition()[0];
EndPosition[1] = this->Interactor->GetEventPosition()[1];
}
vtkInteractorStyleRubberBand3D::OnMouseMove();
}
};
vtkStandardNewMacro(CustomInteractorStyle);
class MainWindow : public QWidget
{
Q_OBJECT
private:
QRect lastSelectionRect;
bool showDebugInfo = true;
public:
MainWindow(QWidget* parent = nullptr) : QWidget(parent)
{
QVBoxLayout* layout = new QVBoxLayout(this);
renderWindow = vtkSmartPointer<vtkGenericOpenGLRenderWindow>::New();
renderer = vtkSmartPointer<vtkRenderer>::New();
renderWindow->AddRenderer(renderer);
vtkWidget = new QVTKOpenGLNativeWidget(this);
vtkWidget->setRenderWindow(renderWindow);
selectButton = new QPushButton("Enter Selection Mode", this);
layout->addWidget(vtkWidget);
layout->addWidget(selectButton);
setLayout(layout);
resize(800, 600);
interactor = vtkWidget->interactor();
defaultStyle = vtkSmartPointer<vtkInteractorStyleTrackballCamera>::New();
selectionStyle = vtkSmartPointer<CustomInteractorStyle>::New();
selectionStyle->SetCurrentRenderer(renderer);
interactor->SetInteractorStyle(defaultStyle);
loadVTUFile();
connect(selectButton, &QPushButton::clicked, this, &MainWindow::toggleSelectionMode);
vtkNew<vtkCallbackCommand> selectionCallback;
selectionCallback->SetCallback(
[](vtkObject* caller, long unsigned int eventId, void* clientData, void* callData)
{
MainWindow* self = static_cast<MainWindow*>(clientData);
self->processSelection();
}
);
selectionCallback->SetClientData(this);
selectionStyle->AddObserver(vtkCommand::SelectionChangedEvent, selectionCallback);
}
private slots:
void toggleSelectionMode()
{
if (interactor->GetInteractorStyle() == defaultStyle)
{
selectButton->setText("Exit Selection Mode");
interactor->SetInteractorStyle(selectionStyle);
}
else
{
selectButton->setText("Enter Selection Mode");
interactor->SetInteractorStyle(defaultStyle);
}
}
private:
void loadVTUFile()
{
reader = vtkSmartPointer<vtkXMLUnstructuredGridReader>::New();
QString fileName =
"path of my vtu.vtu";
reader->SetFileName(fileName.toStdString().c_str());
reader->Update();
vtkSmartPointer<vtkDataSetMapper> mapper = vtkSmartPointer<vtkDataSetMapper>::New();
mapper->SetInputConnection(reader->GetOutputPort());
mainActor = vtkSmartPointer<vtkActor>::New();
mainActor->SetMapper(mapper);
renderer->AddActor(mainActor);
renderer->ResetCamera();
renderer->SetBackground(0.2, 0.3, 0.4);
renderWindow->Render();
double bounds[6];
reader->GetOutput()->GetBounds(bounds);
qDebug() << "Model World Bounds:" << bounds[0] << bounds[1] << bounds[2] << bounds[3] << bounds[4] << bounds[5];
vtkSmartPointer<vtkCoordinate> coordinate = vtkSmartPointer<vtkCoordinate>::New();
coordinate->SetCoordinateSystemToWorld();
int* winSize = renderWindow->GetSize();
int minX = winSize[0], minY = winSize[1], maxX = 0, maxY = 0;
for (int i = 0; i < 8; ++i)
{
double worldPos[3] = {
bounds[i % 2],
bounds[2 + (i / 2) % 2],
bounds[4 + (i / 4) % 2]
};
coordinate->SetValue(worldPos);
int* displayPos = coordinate->GetComputedDisplayValue(renderer);
qDebug() << "World Pos:" << worldPos[0] << worldPos[1] << worldPos[2] << " -> Screen Pos:" << displayPos[0] << displayPos[1];
if (displayPos[0] < minX) minX = displayPos[0];
if (displayPos[0] > maxX) maxX = displayPos[0];
if (displayPos[1] < minY) minY = displayPos[1];
if (displayPos[1] > maxY) maxY = displayPos[1];
}
QRect modelBoundingBox(QPoint(minX, minY), QPoint(maxX, maxY));
qDebug() << "Model Bounding Box on Screen:" << modelBoundingBox;
}
void processSelection()
{
CustomInteractorStyle* style = CustomInteractorStyle::SafeDownCast(interactor->GetInteractorStyle());
if (!style) return;
int* startPos = style->GetStartPosition();
int* endPos = style->GetEndPosition();
int* winSize = renderWindow->GetSize();
lastSelectionRect = QRect(QPoint(std::min(startPos[0], endPos[0]),
std::min(startPos[1], endPos[1])),
QPoint(std::max(startPos[0], endPos[0]),
std::max(startPos[1], endPos[1])));
int x1 = std::min(startPos[0], endPos[0]);
int x2 = std::max(startPos[0], endPos[0]);
int y1 = winSize[1] - std::max(startPos[1], endPos[1]);
int y2 = winSize[1] - std::min(startPos[1], endPos[1]);
qDebug() << "Selection Process:";
qDebug() << " Window Size:" << winSize[0] << winSize[1];
qDebug() << " Original Selection:" << QPoint(startPos[0], startPos[1]) << QPoint(endPos[0], endPos[1]);
qDebug() << " VTK Selection:" << QRect(x1, y1, x2 - x1, y2 - y1);
vtkCamera* camera = renderer->GetActiveCamera();
double viewAngle = camera->GetViewAngle();
double* position = camera->GetPosition();
double* focalPoint = camera->GetFocalPoint();
double* viewUp = camera->GetViewUp();
double viewDir[3] = {
focalPoint[0] - position[0],
focalPoint[1] - position[1],
focalPoint[2] - position[2]
};
vtkMath::Normalize(viewDir);
qDebug() << "Camera Information:";
qDebug() << " Position:" << position[0] << position[1] << position[2];
qDebug() << " View Direction:" << viewDir[0] << viewDir[1] << viewDir[2];
qDebug() << " View Up:" << viewUp[0] << viewUp[1] << viewUp[2];
vtkSmartPointer<vtkHardwareSelector> selector = vtkSmartPointer<vtkHardwareSelector>::New();
selector->SetRenderer(renderer);
selector->SetFieldAssociation(vtkDataObject::FIELD_ASSOCIATION_CELLS);
selector->SetArea(x1, y1, x2, y2);
vtkSmartPointer<vtkSelection> selection;
try
{
renderWindow->Render();
selection = selector->Select();
}
catch (const std::exception& e)
{
qDebug() << "Selection failed:" << e.what();
return;
}
if (!selection || selection->GetNumberOfNodes() == 0)
{
qDebug() << "No elements selected";
return;
}
vtkSmartPointer<vtkExtractSelection> extractSelection = vtkSmartPointer<vtkExtractSelection>::New();
extractSelection->SetInputData(0, reader->GetOutput());
extractSelection->SetInputData(1, selection);
extractSelection->Update();
vtkDataSet* selectedData = vtkDataSet::SafeDownCast(extractSelection->GetOutput());
if (!selectedData || selectedData->GetNumberOfCells() == 0)
{
qDebug() << "No valid cells in selection";
return;
}
vtkSmartPointer<vtkCoordinate> coordinate = vtkSmartPointer<vtkCoordinate>::New();
coordinate->SetCoordinateSystemToWorld();
int minX = winSize[0], minY = winSize[1], maxX = 0, maxY = 0;
for (vtkIdType i = 0; i < selectedData->GetNumberOfCells(); ++i)
{
vtkCell* cell = selectedData->GetCell(i);
for (vtkIdType j = 0; j < cell->GetNumberOfPoints(); ++j)
{
double worldPos[3];
cell->GetPoints()->GetPoint(j, worldPos);
coordinate->SetValue(worldPos);
int* displayPos = coordinate->GetComputedDisplayValue(renderer);
if (displayPos[0] < minX) minX = displayPos[0];
if (displayPos[0] > maxX) maxX = displayPos[0];
if (displayPos[1] < minY) minY = displayPos[1];
if (displayPos[1] > maxY) maxY = displayPos[1];
}
}
QRect selectedBoundingBox(QPoint(minX, minY), QPoint(maxX, maxY));
qDebug() << "Selected Bounding Box:" << selectedBoundingBox;
QRect adjustedSelectionRect = QRect(QPoint(x1, y1), QPoint(x2, y2));
qDebug() << "Adjusted Selection Rect:" << adjustedSelectionRect;
if (adjustedSelectionRect.contains(selectedBoundingBox))
{
qDebug() << "Selection is correct.";
}
else
{
qDebug() << "Selection is incorrect.";
}
vtkSmartPointer<vtkDataSetMapper> selectedMapper = vtkSmartPointer<vtkDataSetMapper>::New();
selectedMapper->SetInputData(selectedData);
vtkSmartPointer<vtkActor> selectedActor = vtkSmartPointer<vtkActor>::New();
selectedActor->SetMapper(selectedMapper);
selectedActor->GetProperty()->SetColor(1.0, 0.0, 0.0);
selectedActor->GetProperty()->SetOpacity(0.7);
renderer->RemoveActor(selectedActor);
renderer->AddActor(selectedActor);
renderWindow->Render();
}
void paintEvent(QPaintEvent* event) override
{
QWidget::paintEvent(event);
if (!showDebugInfo || lastSelectionRect.isNull())
return;
QPainter painter(this);
painter.setPen(QPen(Qt::green, 2));
QRect adjustedRect = QRect(lastSelectionRect.topLeft() + vtkWidget->pos(),
lastSelectionRect.size());
painter.drawRect(adjustedRect);
painter.setPen(Qt::white);
painter.drawText(10, 20, QString("Selection: (%1,%2) to (%3,%4)")
.arg(lastSelectionRect.left())
.arg(lastSelectionRect.top())
.arg(lastSelectionRect.right())
.arg(lastSelectionRect.bottom()));
}
private:
QVTKOpenGLNativeWidget* vtkWidget;
QPushButton* selectButton;
vtkSmartPointer<vtkGenericOpenGLRenderWindow> renderWindow;
vtkSmartPointer<vtkRenderer> renderer;
vtkSmartPointer<vtkRenderWindowInteractor> interactor;
vtkSmartPointer<vtkInteractorStyleTrackballCamera> defaultStyle;
vtkSmartPointer<CustomInteractorStyle> selectionStyle;
vtkSmartPointer<vtkXMLUnstructuredGridReader> reader;
vtkSmartPointer<vtkActor> mainActor;
};
int main(int argc, char* argv[])
{
QApplication app(argc, argv);
MainWindow window;
window.show();
return app.exec();
}
#include "testRectSelect.moc"