一、拾取对象
拾取对象是在PropPickerInteractionStyle类实现的,该类是vtkInteractorStyleRubberBandPick的子类,重写原来的鼠标处理函数。当鼠标左键按下,会触发以下代码:
void PropPickerInteractionStyle::clickSelectGeometry(bool preSelect)
{
int *clickPos = this->GetInteractor()->GetEventPosition(); // 获取鼠标点击位置
int success = -1, index = -1;
vtkActor *actor = nullptr;
if (_selectModel == GeometryPoint || _selectModel == GeometryWinPoint) // 几何点
{
vtkSmartPointer<vtkPointPicker> picker = vtkSmartPointer<vtkPointPicker>::New();
success = picker->Pick(clickPos[0], clickPos[1], 0, this->GetDefaultRenderer());
if (0 != success)
{
index = picker->GetPointId(); // 点的id
actor = picker->GetActor(); // 点对应的Actor
}
}
else
{
vtkSmartPointer<vtkCellPicker> picker = vtkSmartPointer<vtkCellPicker>::New();
if (_selectModel == GeometryCurve || _selectModel == GeometryWinCurve)
picker->SetTolerance(0.0025);
success = picker->Pick(clickPos[0], clickPos[1], 0, this->GetDefaultRenderer());
if (0 != success)
{
index = picker->GetCellId(); // cell的id
actor = picker->GetActor(); // 对应的actor
}
}
emit selectGeometry(preSelect, actor, index);
}
这段代码就是鼠标拾取对象的核心逻辑。其实现是借助了VTK提供的vtkPointPicker、vtkCellPicker。其中当拾取模式是点的时候,使用vtkPointPicker,如果拾取成功返回点的id及对应的Actor(一个Actor可能包含多个点)。当拾取模式是线、面、体的时候,使用vtkCellPicker,成功则返回对应cell id及Actor(一个Actor可能包含多个cell)。注意当拾取线的时候,需要提高拾取Tolerance,不然线很难拾取到。
另外要注意的是,当拾取线的时候,面Actor是拾取不到的,同样当拾取面的时候,线Actor也是拾取不到。这是通过在用户切换选择模式的时候,设置面或边Actor是否可以拾取实现的。核心代码在GeometryViewProvider类中,如下:
void GeometryViewProvider::setGeoSelectMode(int m)
{
_viewData->updateGraphOption();
ModuleBase::SelectModel selectType = (ModuleBase::SelectModel)m;
QList<GeoViewObj> viewObjs = _geoViewHash.values();
vtkActor *actor = nullptr;
for (GeoViewObj vobj : viewObjs)
{
actor = vobj._faceObj.first;
if (actor != nullptr)
actor->SetPickable(false);
actor = vobj._edgeObj.first;
if (actor != nullptr)
actor->SetPickable(false);
actor = vobj._pointObj.first;
if (actor != nullptr)
actor->SetPickable(false);
switch (selectType)
{
case ModuleBase::GeometryBody:
case ModuleBase::GeometrySurface:
case ModuleBase::GeometryWinBody:
case ModuleBase::GeometryWinSurface:
actor = vobj._faceObj.first;
if (actor != nullptr)
actor->SetPickable(true);
break;
case ModuleBase::GeometryWinCurve:
case ModuleBase::GeometryCurve:
actor = vobj._edgeObj.first;
if (actor != nullptr)
actor->SetPickable(true);
break;
case ModuleBase::GeometryWinPoint:
case ModuleBase::GeometryPoint:
actor = vobj._pointObj.first;
if (actor != nullptr)
actor->SetPickable(true);
break;
default:
break;
}
}
}
这个代码相对比较简单,不再分析。
二、从信号到槽函数
拾取对象之后,会发出emit selectGeometry
信号,参数中带有Actor及cell或点的id。这个信号是QT框架提供的编程机制。其对应的槽函数在如下图中能找到:
三、高亮拾取对象
高亮拾取对象处理过程代码如下:
void GeometryViewProvider::selectGeometry(bool pre, vtkActor *ac, int index)
{
if (ac == nullptr || index < 0) // 无效拾取
{
_viewData->preHighLight(nullptr);
_preWindow->reRender();
return;
}
ModuleBase::SelectModel selectMod = _preWindow->getSelectModel(); // 选择模式
vtkDataSet *dataSet = ac->GetMapper()->GetInputAsDataSet(); // 获取Actor的多边形数据集vtkPolyData
vtkPolyData *poly = vtkPolyData::SafeDownCast(dataSet);
if (poly == nullptr)
return;
GeometryViewObject *vobj = nullptr;
switch (selectMod)
{
case ModuleBase::GeometryWinBody:
case ModuleBase::GeometryBody:
vobj = _viewData->getSolidViewObj(poly, index);
break;
case ModuleBase::GeometryWinSurface:
case ModuleBase::GeometrySurface:
vobj = _viewData->getFaceViewObj(poly, index); // 输入多边形数据+cell id
break;
case ModuleBase::GeometryWinCurve:
case ModuleBase::GeometryCurve:
vobj = _viewData->getEdgeViewObj(poly, index);
break;
case ModuleBase::GeometryWinPoint:
case ModuleBase::GeometryPoint:
vobj = _viewData->getPointViewObj(poly, index);
break;
default:
break;
}
if (vobj == nullptr)
return;
if (pre)
_viewData->preHighLight(vobj); // 设置预高亮
else
{
vobj->highLight(); // 高亮对象
_viewData->preHighLight(nullptr);
emit geoShapeSelected(vobj->getGeometySet(), vobj->getIndex());
}
_preWindow->reRender();
}
同样,这里也只分析面的高亮,其他的高亮逻辑类似。高亮处理的时候,直接先根据Actor获取了其vtkPolyData数据集,找到哪个面需要高亮,最后更改其颜色。如何根据cell id找到这个面,需要理解其数据结构才能看懂其逻辑。对于面的数据结构,这篇文章【FastCAE源码阅读3】几何模型显示:从OCC对象到VTK对象有详细描述。其核心原理是查找cell id落在哪个面包含的cell区间上,确定高亮哪个面。最后调用_preWindow->reRender()重新绘制整个场景。