• 【FastCAE源码阅读9】鼠标框选网格、节点的实现


    一、VTK的框选支持类vtkInteractorStyleRubberBandPick

    FastCAE的鼠标事件交互类是PropPickerInteractionStyle,它扩展自vtkInteractorStyleRubberBandPick。vtkInteractorStyleRubberBandPick类可以实现鼠标框选物体,默认情况下按下键盘r键开启框选模式,这时拖动鼠标可拾取物体。VTK官网有其例子:HighlightSelection

    二、FastCAE框选产品设计

    我们看FastCAE的鼠标拾取产品设计。其只支持框选网格点与网格单元,几何点、线、面都不支持。框选网格单元效果如下:
    请添加图片描述

    在VTK的给的案例中,按r键是为了打开vtkInteractorStyleRubberBandPick类的框选开关,设置vtkInteractorStyleRubberBandPick字段CurrentMode=1(VTKISRBP_SELECT),表示开启框选模式。VTK中vtkInteractorStyleRubberBandPick.cxx源码如下:

    void vtkInteractorStyleRubberBandPick::OnChar()
    {
      switch (this->Interactor->GetKeyCode())
      {
        case 'r':
        case 'R':
          // r toggles the rubber band selection mode for mouse button 1
          if (this->CurrentMode == VTKISRBP_ORIENT)
          {
            this->CurrentMode = VTKISRBP_SELECT;
          }
          else
          {
            this->CurrentMode = VTKISRBP_ORIENT;
          }
          break;
        case 'p':
        case 'P':
        {
          vtkRenderWindowInteractor* rwi = this->Interactor;
          int* eventPos = rwi->GetEventPosition();
          this->FindPokedRenderer(eventPos[0], eventPos[1]);
          this->StartPosition[0] = eventPos[0];
          this->StartPosition[1] = eventPos[1];
          this->EndPosition[0] = eventPos[0];
          this->EndPosition[1] = eventPos[1];
          this->Pick();
          break;
        }
        default:
          this->Superclass::OnChar();
      }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33

    而在FastCAE框选时,不需要按下r键,其原因在用切换拾取模式时,直接改掉了CurrentMode的值,源码如下(注意看这个函数最后一行):

    void PropPickerInteractionStyle::setSelectModel(int m)
    {
    	_selectModel = (SelectModel)m;
    	this->CurrentMode = 0;
    	if (_actor != nullptr)
    		_actor->GetProperty()->DeepCopy(_property);
    	_actor = nullptr;
    	_preGeoSeltctActor = nullptr;
    	_selectItems.clear();
    	emit grabKeyBoard(false);
    	switch (_selectModel)
    	{
    	case ModuleBase::MeshNode:
    	case ModuleBase::MeshCell:
    	case ModuleBase::GeometryWinPoint:
    	case ModuleBase::GeometryWinCurve:
    	case ModuleBase::GeometryWinSurface:
    	case ModuleBase::GeometryWinBody:
    		emit grabKeyBoard(true);
    		break;
    	case ModuleBase::GeometryPoint:
    	case ModuleBase::GeometryCurve:
    	case ModuleBase::GeometrySurface:
    	case ModuleBase::GeometryBody:
    		break;
    	case ModuleBase::BoxMeshNode: // 当选择方式是框选时,直接设置开始框选
    	case ModuleBase::BoxMeshCell:
    	case ModuleBase::DrawSketch:
    		this->CurrentMode = 1;
    		break;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    这种设计的好处是不用按键盘进行交互,但造成激活框选按钮之后,视图的角度无法更改。本来按住左键拖拽可以旋转视图的,打开框选之后就失效了。

    其框选还有一个比较严重的问题:框选会同时拾取物体的表面与背面单元,效果如下:
    请添加图片描述

    这种效果惊不惊喜,意不意外!?很多场景下,这种拾取是不满足要求的。进一步分析其框选逻辑就很好理解这种现象了。

    三、框选计算逻辑

    PropPickerInteractionStyle::OnLeftButtonUp()处理鼠标抬起事件,框选计算哪些物体要被选中的逻辑也在这里被触发。

    void PropPickerInteractionStyle::OnLeftButtonUp()
    {
    	vtkInteractorStyleRubberBandPick::OnLeftButtonUp();
    	if (_selectModel == None && !_mouseMoved)
    		emit this->clearAllHighLight();
    	if ((_selectModel != BoxMeshCell) && (_selectModel != BoxMeshNode) && (_selectModel != DrawSketch))
    		return;
    	if (this->CurrentMode == 0)
    		return;
    	//		_selectItemIDs->SetNumberOfValues(0);
    	_selectItems.clear();
    	int *endPos = this->GetInteractor()->GetEventPosition();
    	_endPos[0] = endPos[0];
    	_endPos[1] = endPos[1];
    	//		qDebug() << "end  " << _endPos[0] << "   " << _endPos[1];
    	if (_selectModel != DrawSketch)
    	{
    		vtkActor *ac = nullptr;
    		vtkAreaPicker *areaPicker = dynamic_cast<vtkAreaPicker *>(this->GetInteractor()->GetPicker());
    		ac = areaPicker->GetActor();
    		if (ac == nullptr)
    			return;
    	}
    	switch (_selectModel)
    	{
    	case ModuleBase::BoxMeshNode: // 计算哪些节点被选中
    		boxSelectMeshNode();
    		break;
    	case ModuleBase::BoxMeshCell: // 计算哪些单元要被选中
    		boxSelectMeshCell();
    		break;
    	case ModuleBase::DrawSketch:
    		_coordinate->SetCoordinateSystemToDisplay();
    		_coordinate->SetValue(endPos[0], endPos[1], 0);
    		double *d = _coordinate->GetComputedWorldValue(_renderer);
    		emit mouseReleasePoint(d);
    		break;
    	}
    	_mouseMoved = false;
    	_leftButtonDown = false;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41

    boxSelectMeshNode()、boxSelectMeshCell()函数分别哪些节点、单元要被拾取。

    void PropPickerInteractionStyle::boxSelectMeshNode()
    {
    	emit clearAllHighLight(); // 清除掉当前高亮
    	_selectItems.clear(); // 清理当前选择项
    	// Forward events
    	int range[4];
    	this->getBoxRange(range);  // 获取框选矩形的坐标
    	vtkActorCollection *actors = _renderer->GetActors(); // 获取当前的场景中所有actor
    	actors->InitTraversal();
    	const int nac = actors->GetNumberOfItems();
    	for (int i = 0; i < nac; ++i) // 对Actor进行遍历
    	{
    		vtkActor *actor = actors->GetNextActor();
    		if (actor == nullptr)
    			if (!actor->GetVisibility())
    				continue;
    		if (!actor->GetPickable())
    			continue;
    		vtkMapper *mapper = actor->GetMapper();
    		if (mapper == nullptr)
    			continue;
    		vtkDataSet *dataset = mapper->GetInputAsDataSet();
    		if (dataset == nullptr)
    			continue;
    		vtkDataArray *IDS = dataset->GetPointData()->GetArray("IDS"); // 提取Actor的点数据
    		if (IDS == nullptr)
    			continue;
    		this->selectMesh(dataset, range);
    	}
    	emit highLight(&_selectItems);
    }
    
    void PropPickerInteractionStyle::selectMesh(vtkDataSet *dataSet, int *range)
    {
    	vtkRenderer *render = this->GetInteractor()->GetRenderWindow()->GetRenderers()->GetFirstRenderer();
    	vtkSmartPointer<vtkCoordinate> coordinate = vtkSmartPointer<vtkCoordinate>::New();
    	coordinate->SetCoordinateSystemToWorld();
    	coordinate->GetComputedDisplayValue(render);
    
    	if (_selectModel == BoxMeshNode)
    	{
    		vtkDataArray *ids = dataSet->GetPointData()->GetArray("IDS"); // 获取点集
    		const int npoint = dataSet->GetNumberOfPoints();
    		for (int i = 0; i < npoint; ++i)
    		{
    			double coor[3];
    			dataSet->GetPoint(i, coor); // 获取点的坐标
    			coordinate->SetValue(coor); // 将点的坐标设置给coordinate
    			int *va = coordinate->GetComputedDisplayValue(render); // 计算屏幕坐标
    			if (isPointInRange(va, range)) // 是否在鼠标框内部
    			{
    				double *k_id = ids->GetTuple2(i); // 看不懂?
    				_selectItems.insert(k_id[0], k_id[1]);
    			}
    		}
    	}
    	else if (_selectModel == BoxMeshCell)
    	{
    		vtkDataArray *ids = dataSet->GetCellData()->GetArray("IDS"); // 获取cell数据
    		const int ncell = dataSet->GetNumberOfCells();
    		for (int i = 0; i < ncell; ++i) // 遍历cell
    		{
    			vtkCell *cell = dataSet->GetCell(i); // 当前的cell
    			double pcenter[3] = {0};
    			cell->GetParametricCenter(pcenter); // 获取当前cell的中心点参数坐标
    			int subid;
    			double coor[3];
    			double w[100];
    			cell->EvaluateLocation(subid, pcenter, coor, w); // 根据参数坐标获取中心点世界空间坐标
    			coordinate->SetValue(coor);
    			int *va = coordinate->GetComputedDisplayValue(render); // 计算屏幕坐标
    			if (isPointInRange(va, range)) // 屏幕坐标是否在选择框内
    			{
    				double *k_id = ids->GetTuple2(i);
    				_selectItems.insert(k_id[0], k_id[1]);
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    根据以上代码,框选点时,直接根据点坐标计算其投影到屏幕上的坐标,判断是否在选择框内。单元是判断中心点是否在选择框内部。因为投影之后,丢弃了深度方向的信息,没有考虑物体的遮挡信息,所以框选时表面、背面均可选择。而且其计算框选时遍历所有网格,没有借助一些加速结构,如BVH树等,造成框选效率较低,当网格数量较多时,这种方式很慢。

    总结:

    FastCAE的框选逻辑过于简单,只是demo阶段,实际的CAE软件的拾取逻辑要远比这复杂。

  • 相关阅读:
    【Paper Note】利用Boundary-aware Attention边界感知注意力机制增强部分伪造音频定位
    Streptavidin-MAL,Maleimide 马来酰亚胺修饰/标记/偶联链霉亲和素
    【数字化转型】10大数字化转型能力成熟度模型03
    出租屋智能视频监控系统方案:全面保卫租客安全
    基于Python深度学习的文字检测识别系统
    多行多列按钮多选1
    太速科技-基于3U VPX的 Jetson Xavier NX GPU计算主板
    Sealos 云主机正式上线,便宜,便宜,便宜!
    关于利用卡诺图快速解决时序电路自启动问题的研究
    TikTok矩阵系统功能怎么写?常用源代码是什么?
  • 原文地址:https://blog.csdn.net/loveoobaby/article/details/134404690