VTK知识学习(54)- 交互与Widget(五)

1、前言

选择拾取是人机交互过程的一个重要功能。在玩3D游戏时,场景中可能会存在多个角色,有时需要使用鼠标来选择所要控制的角色,这就需要用到拾取功能。另外,在某些三维图形的编辑软件中,经常需要编辑其中的一个点、一个面片或者一个局部区域,这也需要通过拾取功能来完成。VTK中定义了多个拾取功能的类,下图显示了这些拾取类的继承关系。VTK中的所有拾取类都继承自vkAbstractPicker 类,利用这些类可以实现许多复杂的功能。

2、点拾取

1)概述

从图上可以看出,完成点拾取功能类是vtkPointPicker。VTK中的消息是通过vtkRenderWindowInteractor 类来处理的,在类 vtkRenderWindowInteractor 中,有如下函数:

public virtual void SetPicker(vtkAbstractPicker arg0);

该函数用来设置具体的 vkAbstractPicker 对象执行对应的拾取操作,对于点拾取就是设置vtkPointPicker 对象。

vtkRenderWindowInteractor 内部定义了一个 vtkInteractorStyle 对象。vtkInteractorStyle 类是一个虚基类,其子类定义了多种鼠标和键盘消息的处理方法,在实现拾取操作时,需要定制相应的鼠标消息处理函数。比如拾取某个点时,应该响应鼠标的左键按下消息,并在响应该消息的函数中根据鼠标的当前窗口坐标来完成拾取操作。

2)代码
cs 复制代码
private void TestPointPicker()
 {
     vtkSphereSource sphereSource = vtkSphereSource.New();
     sphereSource.Update();

     //create a mapper and actor
     vtkPolyDataMapper mapper = vtkPolyDataMapper.New();
     mapper.SetInputConnection(sphereSource.GetOutputPort());
     vtkActor actor = vtkActor.New();
     actor.SetMapper(mapper);

     //creat a renderer,render window,and interactor
     vtkRenderer renderer = vtkRenderer.New();
     vtkRenderWindow renderWindow = vtkRenderWindow.New();
     renderWindow.Render();
     renderWindow.SetWindowName("PointPicker");
     renderWindow.AddRenderer(renderer);

     vtkPointPicker pointPicker = vtkPointPicker.New();

     vtkRenderWindowInteractor windowInteractor = vtkRenderWindowInteractor.New();
     windowInteractor.SetPicker(pointPicker);
     windowInteractor.SetRenderWindow(renderWindow);
     windowInteractor.LeftButtonPressEvt += WindowInteractor_PickEvt;
     vtkInteractorStyleTrackballCamera style = new vtkInteractorStyleTrackballCamera();
     windowInteractor.SetInteractorStyle(style);

     renderer.AddActor(actor);
     renderer.SetBackground(1, 1, 1);

     renderWindow.Render();
     windowInteractor.Start();
 }
cs 复制代码
 private void WindowInteractor_PickEvt(vtkObject sender, vtkObjectEventArgs e)
        {
            if (sender is vtkRenderWindowInteractor interactor)
            {
                int[] pos = interactor.GetEventPosition();
                Console.WriteLine($"Picking pixel:  {pos[0]} {pos[1]}");

                // 拾取函数 4个参数 前三个为鼠标的当前窗口坐标,第四个参数是vktRenderer对象
                interactor.GetPicker().Pick(pos[0], pos[1], 0, interactor.GetRenderWindow().GetRenderers().GetFirstRenderer());
                double[] picked = interactor.GetPicker().GetPickPosition();
                Console.WriteLine($"Picked value:  {picked[0]} {picked[1]} {picked[2]}");

                vtkSphereSource sphereSource = vtkSphereSource.New();
                sphereSource.Update();

                vtkPolyDataMapper mapper = vtkPolyDataMapper.New();
                mapper.SetInputConnection(sphereSource.GetOutputPort());
                vtkActor actor = vtkActor.New();
                actor.SetMapper(mapper);
                actor.SetPosition(picked[0], picked[1], picked[2]);
                actor.SetScale(0.05);
                actor.GetProperty().SetColor(1, 0, 0);
                interactor.GetRenderWindow().GetRenderers().GetFirstRenderer().AddActor(actor);
            }
        }
3)效果

4)说明

这里是注册LeftButtonPressEvt事件响应函数。在事件函数中,调用了vtkRenderWindowInteractor 的GetEventPosition(函数输出鼠标点击的屏幕坐标(以像素为单位)。实现拾取的函数是:

public virtual int Pick(double selectionX, double selectionY, double selectionZ, vtkRenderer renderer);

该函数需要接收四个参数,前三个为(selectionX,selectionY,selectionZ),即鼠标的当前窗口坐标,其中selectionZ通常为0:第四个参数是vtkRenderer对象。

GetPickPosition()函数输出鼠标当前单击位置的世界坐标系下的坐标值。为了更加直观地显示鼠标左键按下的位置,在鼠标的单击位置生成了一个小红球。

3、单元拾取

1)概述

vtkCellPicker 类用于拾取模型中的某个单元。

2)代码
cs 复制代码
private void TestCellPicker()
        {
            vtkSphereSource sphereSource = vtkSphereSource.New();
            sphereSource.Update();
            polyData = sphereSource.GetOutput();
            selectedMapper = vtkDataSetMapper.New();
            selectedActor = vtkActor.New();

            //create a mapper and actor
            vtkPolyDataMapper mapper = vtkPolyDataMapper.New();
            mapper.SetInputConnection(sphereSource.GetOutputPort());
            vtkActor actor = vtkActor.New();
            actor.SetMapper(mapper);
            actor.GetProperty().SetColor(0, 1, 0);

            //creat a renderer,render window,and interactor
            vtkRenderer renderer = vtkRenderer.New();

            vtkRenderWindow renderWindow = vtkRenderWindow.New();
            renderWindow.Render();
            renderWindow.SetWindowName("CellPicker");
            renderWindow.AddRenderer(renderer);

            vtkCellPicker cellPicker = vtkCellPicker.New();

            vtkRenderWindowInteractor windowInteractor = vtkRenderWindowInteractor.New();
            windowInteractor.SetPicker(cellPicker);
            windowInteractor.SetRenderWindow(renderWindow);
            windowInteractor.LeftButtonPressEvt += WindowInteractor_LeftButtonPressEvt;
            vtkInteractorStyleTrackballCamera style = new vtkInteractorStyleTrackballCamera();
            windowInteractor.SetInteractorStyle(style);

            renderer.AddActor(actor);
            renderer.SetBackground(1, 1, 1);

            renderWindow.Render();
            windowInteractor.Initialize();
            windowInteractor.Start();
        }
cs 复制代码
  private void WindowInteractor_LeftButtonPressEvt(vtkObject sender, vtkObjectEventArgs e)
        {
            if (sender is vtkRenderWindowInteractor interactor)
            {
                int[] pos = interactor.GetEventPosition();
                Console.WriteLine($"Picking pixel:  {pos[0]} {pos[1]}");
                vtkCellPicker pick = interactor.GetPicker() as vtkCellPicker;

                // 拾取函数 4个参数 前三个为鼠标的当前窗口坐标,第四个参数是vktRenderer对象
                pick.Pick(pos[0], pos[1], 0, interactor.GetRenderWindow().GetRenderers().GetFirstRenderer());

                if (pick.GetCellId() != -1)
                {
                    vtkIdTypeArray ids = vtkIdTypeArray.New();
                    ids.SetNumberOfComponents(1);

                    //拾取完毕,通过GetCellId()函数来得到当前拾取的单元索引号。
                    ids.InsertNextTuple1(pick.GetCellId());

                    //vtkPolyData的局部数据提取功能

                    //声明了要提取的数据的类型
                    vtkSelectionNode selectionNode = vtkSelectionNode.New();
                    selectionNode.SetFieldType((int)vtkSelectionNode.SelectionField.CELL); //设置数据的类型为单元
                    selectionNode.SetContentType((int)vtkSelectionNode.SelectionContent.INDICES); //设置数据的内容为索引号
                    selectionNode.SetSelectionList(ids);

                    vtkSelection selection = vtkSelection.New();
                    selection.Union(selectionNode);

                    //实现了数据提取功能
                    vtkExtractSelection extractSelection = vtkExtractSelection.New();
                    extractSelection.SetInputData(0, polyData);
                    extractSelection.SetInputData(1, selection);
                    extractSelection.Update();

                    selectedMapper.SetInputData(extractSelection.GetOutput() as vtkDataSet);
                    selectedActor.SetMapper(selectedMapper);
                    selectedActor.GetProperty().EdgeVisibilityOn();
                    selectedActor.GetProperty().SetEdgeColor(1, 0, 0);
                    selectedActor.GetProperty().SetLineWidth(3);

                    interactor.GetRenderWindow().GetRenderers().GetFirstRenderer().AddActor(selectedActor);
                }
            }
        }
3)效果

4)说明

通过注册vtkRenderWindowInteractor的LeftButtonPressEvt事件响应函数。polyData 为被拾取的模型数据,需要通过外部设置。在响应鼠标左键消息时,首先定义了vtkCellPicker 对象,使用 Pick()函数实现拾取功能。拾取完毕,即可通过 GetCelld()函数来得到当前拾取的单元索引号。为了更方便地显示拾取的结果,可实现单元边的高亮显示。这里就涉及了vtkPolyData的局部数据提取功能。实现该功能时使用了几个新的类。vtkIdTypeAray对象存储当前选中的单元的索引号,每次只选择一个单元,因此每次该对象仅有一个索引号;vtkSelectionNode 对象与 vtkSelection 对象通常搭配使用,vtkSelection 实际上是一个 vtkSelectionNode 的数组,而vtkSelectionNode 则声明了要提取的数据的类型,这里 SetFieldType()设置数据的类型为单元,SetContentType()设置数据的内容为索引号。vtkExtractSelection 实现了数据提取功能,其第一个输入为被提取的 vtkPolyData 数据,第二个输入为 vkSelection 对象,标记要提取的数据类型。提取完毕,即可将提取的结果保存至一个vtkActor 对象,并添加至当前的 vtkRenderer中显示。

2、Prop拾取

1)概述

在渲染场景中拾取某一Prop对象时,使用的类是 vtkPropPicker。

2)代码
cs 复制代码
 private void TestPropPicker()
        {
            vtkRenderer renderer = vtkRenderer.New();
            vtkRenderWindow renderWindow = vtkRenderWindow.New();
            renderWindow.Render();
            renderWindow.SetWindowName("PropPicker");
            renderWindow.AddRenderer(renderer);

            vtkRenderWindowInteractor windowInteractor = vtkRenderWindowInteractor.New();
            windowInteractor.SetRenderWindow(renderWindow);
            lastPickedProperty = vtkProperty.New();
            windowInteractor.LeftButtonPressEvt += WindowInteractor_PropPickerEvt;
            for (int i = 0; i < 10; i++)
            {
                vtkSphereSource source = vtkSphereSource.New();
                double x, y, z, radius;
                x = vtkMath.Random(-5, 5);
                y = vtkMath.Random(-5, 5);
                z = vtkMath.Random(-5, 5);
                radius = vtkMath.Random(0.5, 1.0);
                source.SetRadius(radius);
                source.SetCenter(x, y, z);
                source.SetPhiResolution(11);
                source.SetThetaResolution(21);
                vtkPolyDataMapper mapper = vtkPolyDataMapper.New();
                mapper.SetInputConnection(source.GetOutputPort());
                vtkActor actor = vtkActor.New();
                actor.SetMapper(mapper);
                double r, g, b;
                r = vtkMath.Random(0.4, 1.0);
                g = vtkMath.Random(0.4, 1.0);
                b = vtkMath.Random(0.4, 1.0);
                actor.GetProperty().SetDiffuseColor(r, g, b);
                actor.GetProperty().SetDiffuse(0.8);
                actor.GetProperty().SetSpecular(0.5);
                actor.GetProperty().SetSpecularColor(1, 1, 1);
                actor.GetProperty().SetSpecularPower(30);
                renderer.AddActor(actor);
            }
            renderer.SetBackground(1, 1, 1);

            renderWindow.Render();
            windowInteractor.Initialize();
            windowInteractor.Start();
        }
cs 复制代码
  private void WindowInteractor_PropPickerEvt(vtkObject sender, vtkObjectEventArgs e)
        {
            /*
             *  首先获取鼠标单击的坐标值,然后实例化一个vtkPropPicker对象,并调用Pick()函数实现Prop的拾取。
             *  该类的主要功能是当用户单击渲染场景中的某个对象时,对所拾取的对象进行红色高亮显示。
             *  为了便于恢复Actor原来的属性设置,程序中先储存当前拾取的Actorn属性值到LastPciedProperty中。
             */
            if (sender is vtkRenderWindowInteractor interactor)
            {
                int[] clickPos = interactor.GetEventPosition();

                //pick from this location.
                vtkPropPicker picker = vtkPropPicker.New();
                picker.Pick(clickPos[0], clickPos[1], 0, interactor.GetRenderWindow().GetRenderers().GetFirstRenderer());

                double[] pos = picker.GetPickPosition();
                if (lastPickedActor != null)
                {
                    lastPickedActor.GetProperty().DeepCopy(lastPickedProperty);
                }
                lastPickedActor = picker.GetActor();
                if (lastPickedActor != null)
                {
                    // Save the property of the picked actor so that we can restore it next time
                    // 保存选取的 actor 的属性,以便下次可以恢复它
                    lastPickedProperty.DeepCopy(lastPickedActor.GetProperty());

                    // Highlight the picked actor by changing its properties
                    //通过更改其属性高亮显示选取的角色
                    lastPickedActor.GetProperty().SetColor(1, 0, 0);
                    lastPickedActor.GetProperty().SetDiffuse(1);
                    lastPickedActor.GetProperty().SetSpecular(0);
                }
            }
        }
3)效果
4)说明

通过注册vtkRenderWindowInteractor的LeftButtonPressEvt事件响应函数。

首先获取鼠标单击的坐标值,然后实例化一个 vtkPropPicker对象,并调用Pick()函数实现Prop的拾取。

该类的主要功能是当用户单击渲染场景中的某个对象时,对所拾取的对象进行红色高亮显示。

为了便于恢复Actor原来的属性设置,程序中先储存当前拾取的Actor属性值到LastPickedProperty中,以便在下次拾取其他 Actor 对象时,将先前所拾取的对象恢复到原来的属性。

PropPickerInteractorStyle 类在 main()函数的使用方法与以上两例相似。

其他的拾取类,如vkAreaPicker可以根据用户提供的矩形框来选择该矩形框范围内的vtkActor/vtkProp 对象;vtkWroldPointPicker 可以实现窗口坐标到世界坐标的转换等,实现方法基本都是一致的。实现的关键在于定义和消息处理。

相关推荐
m0_719817111 小时前
Linux运维新人自用笔记(用虚拟机Ubuntu部署lamp环境,搭建WordPress博客)
linux·学习
小馒头君君4 小时前
近期GitHub热榜推荐
开发语言·windows·python·学习·github
iFulling4 小时前
【单片机】51单片机学习笔记
单片机·学习·51单片机
武昌库里写JAVA5 小时前
关于springcloud的坑
java·开发语言·spring boot·学习·课程设计
小浪学编程5 小时前
C#学习13——正则表达式
学习·正则表达式
武昌库里写JAVA6 小时前
大模型更重要关注工艺
java·开发语言·spring boot·学习·课程设计
幻奏岚音6 小时前
Java数据结构——第一章Java基础回顾
java·开发语言·jvm·笔记·学习
xiaoxiaoxiaolll7 小时前
5G光网络新突破:<Light: Science & Applications>报道可适应环境扰动的DRC实时校准技术
人工智能·学习
蓬荜生灰10 小时前
AD学习(4)
学习