Error area picking with vtk and qt

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"

Hello,

Can you, please, post a screen cap to illustrate the issue?

best,

PC

Thank you for your reply. Just like this

Thank you. The video added.

Hello,

You’re creating a second actor with coincident geometry to render the selection. So, there is likely z-fighting or depth-fighting going on between the model and the selection actors. Please, take a look at this: Visualization failed when display two polygon in the same time - Support - VTK . Particularly, see this post: Visualization failed when display two polygon in the same time - #9 by zhang-qiang-github (map2 in that post is a vtkMapper).

best,

PC

Thank you for your reply. I tried using the SetResolveCoincidentTopologyPolygonOffsetParameters method, which only improved the display effect, but the selected cells are still not the ones I boxed. I tried writing these selected cells to a VTU file and found that these cells are consistent with the highlighted cells above. So, the current problem is that the boxed cells are not within the box, rather than a display issue.

If I use a 3D plane, the selection and highlighting of cells are normal. But if I use other complex 3D models, the selection and highlighting of cells are incorrect. Below is the operation video and my code.

#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 <vtkXMLUnstructuredGridWriter.h>
#include <vtkHardwareSelector.h>
#include <vtkSelection.h>
#include <vtkExtractSelection.h>
#include <vtkCallbackCommand.h>
#include <vtkInteractorStyleTrackballCamera.h>
#include <qdebug.h>
#include <qpainter.h>
#include <vtkCamera.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
    {
        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;
    vtkSmartPointer<vtkActor> previousSelectedActor;
    bool isSelecting = false;

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);
            isSelecting = true;
        }
        else
        {
            selectButton->setText("Enter Selection Mode");
            interactor->SetInteractorStyle(defaultStyle);
            isSelecting = false;
        }
    }

private:
    void loadVTUFile()
    {
        reader = vtkSmartPointer<vtkXMLUnstructuredGridReader>::New();
        QString fileName =
            "path to my vtu file";
        
        reader->SetFileName(fileName.toStdString().c_str());
        reader->Update();

        vtkSmartPointer<vtkDataSetMapper> mapper = vtkSmartPointer<vtkDataSetMapper>::New();
        mapper->SetInputConnection(reader->GetOutputPort());
        mapper->SetResolveCoincidentTopologyToPolygonOffset();
        mapper->SetResolveCoincidentTopologyPolygonOffsetParameters(0, 0);

        mainActor = vtkSmartPointer<vtkActor>::New();
        mainActor->SetMapper(mapper);

        renderer->AddActor(mainActor);

        vtkCamera *camera = renderer->GetActiveCamera();
        camera->SetViewUp(0, 1, 0);
        camera->SetPosition(2344.01, -740.319, 1496.1);
        camera->SetFocalPoint(2344.01, -740.319, 0);

        renderer->ResetCamera();
        renderer->ResetCameraClippingRange();
        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];
    }

    QList<vtkSmartPointer<vtkActor>> selectedActors;

    void clearSelectedActors()
    {
        for (auto actor : selectedActors)
        {
            renderer->RemoveActor(actor);
        }
        selectedActors.clear();
        if (previousSelectedActor)
        {
            renderer->RemoveActor(previousSelectedActor);
            previousSelectedActor = nullptr;
        }
        renderWindow->Render();
    }

    void processSelection()
    {
        clearSelectedActors();

        CustomInteractorStyle *style = CustomInteractorStyle::SafeDownCast(interactor->GetInteractorStyle());
        if (!style)
            return;

        int *startPos = style->GetStartPosition();
        int *endPos = style->GetEndPosition();

        int x1 = std::min(startPos[0], endPos[0]);
        int x2 = std::max(startPos[0], endPos[0]);
        int y1 = std::min(startPos[1], endPos[1]);
        int y2 = std::max(startPos[1], endPos[1]);

        vtkSmartPointer<vtkHardwareSelector> selector = vtkSmartPointer<vtkHardwareSelector>::New();
        selector->SetRenderer(renderer);
        selector->SetFieldAssociation(vtkDataObject::FIELD_ASSOCIATION_CELLS);
        selector->SetArea(x1, y1, x2, y2);

        renderWindow->SwapBuffersOff();
        renderWindow->Render();

        vtkSmartPointer<vtkSelection> selection;
        try
        {
            selection = selector->Select();
        }
        catch (const std::exception &e)
        {
            qDebug() << "Selection failed:" << e.what();
            renderWindow->SwapBuffersOn();
            return;
        }
        renderWindow->SwapBuffersOn();

        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();

        vtkUnstructuredGrid *selectedData = vtkUnstructuredGrid::SafeDownCast(extractSelection->GetOutput());
        if (!selectedData || selectedData->GetNumberOfCells() == 0)
        {
            qDebug() << "No valid cells in selection";
            return;
        }

        vtkSmartPointer<vtkDataSetMapper> selectedMapper = vtkSmartPointer<vtkDataSetMapper>::New();
        selectedMapper->SetInputData(selectedData);
        selectedMapper->SetResolveCoincidentTopologyToPolygonOffset();
        selectedMapper->SetResolveCoincidentTopologyPolygonOffsetParameters(1, 1);

        vtkSmartPointer<vtkActor> selectedActor = vtkSmartPointer<vtkActor>::New();
        selectedActor->SetMapper(selectedMapper);
        selectedActor->GetProperty()->SetColor(1.0, 0.0, 0.0);
        selectedActor->GetProperty()->SetRepresentationToWireframe();
        selectedActor->GetProperty()->SetLineWidth(2);
        selectedActor->GetProperty()->SetOpacity(0.7);

        selectedActors.append(selectedActor);
        renderer->AddActor(selectedActor);
        renderWindow->Render();
    }

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"