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)代码
private void TestPointPicker(){vtkSphereSource sphereSource = vtkSphereSource.New();sphereSource.Update();//create a mapper and actorvtkPolyDataMapper mapper = vtkPolyDataMapper.New();mapper.SetInputConnection(sphereSource.GetOutputPort());vtkActor actor = vtkActor.New();actor.SetMapper(mapper);//creat a renderer,render window,and interactorvtkRenderer 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();}
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)代码
private void TestCellPicker(){vtkSphereSource sphereSource = vtkSphereSource.New();sphereSource.Update();polyData = sphereSource.GetOutput();selectedMapper = vtkDataSetMapper.New();selectedActor = vtkActor.New();//create a mapper and actorvtkPolyDataMapper 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 interactorvtkRenderer 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();}
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)代码
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();}
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 可以实现窗口坐标到世界坐标的转换等,实现方法基本都是一致的。实现的关键在于定义和消息处理。