#include "nmVTKPlanarPolygonInteractorStyle.h" #include #include #include #include #include #include #include #include vtkStandardNewMacro(nmVTKPlanarPolygonInteractorStyle); namespace { const double PLANAR_POLYGON_RAY_EPSILON = 1.0e-12; bool isFinitePoint(const QPointF& oPoint) { return vtkMath::IsFinite(oPoint.x()) && vtkMath::IsFinite(oPoint.y()); } bool displayToWorld(vtkRenderer* pRenderer, double dX, double dY, double dZ, double dWorld[3]) { if(pRenderer == nullptr || dWorld == nullptr || !vtkMath::IsFinite(dX) || !vtkMath::IsFinite(dY) || !vtkMath::IsFinite(dZ)) { return false; } // VTK 返回齐次世界坐标,必须先除以 w 才能参与射线计算。 pRenderer->SetDisplayPoint(dX, dY, dZ); pRenderer->DisplayToWorld(); const double* pHomogeneous = pRenderer->GetWorldPoint(); if(pHomogeneous == nullptr || !vtkMath::IsFinite(pHomogeneous[0]) || !vtkMath::IsFinite(pHomogeneous[1]) || !vtkMath::IsFinite(pHomogeneous[2]) || !vtkMath::IsFinite(pHomogeneous[3]) || std::fabs(pHomogeneous[3]) <= std::numeric_limits::epsilon()) { return false; } const double dInverseW = 1.0 / pHomogeneous[3]; dWorld[0] = pHomogeneous[0] * dInverseW; dWorld[1] = pHomogeneous[1] * dInverseW; dWorld[2] = pHomogeneous[2] * dInverseW; return vtkMath::IsFinite(dWorld[0]) && vtkMath::IsFinite(dWorld[1]) && vtkMath::IsFinite(dWorld[2]); } double coordinateScale(const QPointF& oLeft, const QPointF& oRight) { double dScale = 1.0; dScale = qMax(dScale, std::fabs(oLeft.x())); dScale = qMax(dScale, std::fabs(oLeft.y())); dScale = qMax(dScale, std::fabs(oRight.x())); dScale = qMax(dScale, std::fabs(oRight.y())); return dScale; } bool isSamePoint(const QPointF& oLeft, const QPointF& oRight) { const double dScale = coordinateScale(oLeft, oRight); const double dTolerance = 1.0e-10 * dScale; const double dX = oLeft.x() - oRight.x(); const double dY = oLeft.y() - oRight.y(); return dX * dX + dY * dY <= dTolerance * dTolerance; } bool hasNonZeroArea(const QVector& vecPoints) { if(vecPoints.size() < 3) { return false; } // 以首点为局部原点计算面积,避免大坐标下的精度损失。 const QPointF oOrigin = vecPoints.first(); double dMinimumX = oOrigin.x(); double dMaximumX = oOrigin.x(); double dMinimumY = oOrigin.y(); double dMaximumY = oOrigin.y(); double dTwiceArea = 0.0; for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) { const QPointF& oCurrent = vecPoints[nIndex]; const QPointF& oNext = vecPoints[(nIndex + 1) % vecPoints.size()]; const double dCurrentX = oCurrent.x() - oOrigin.x(); const double dCurrentY = oCurrent.y() - oOrigin.y(); const double dNextX = oNext.x() - oOrigin.x(); const double dNextY = oNext.y() - oOrigin.y(); dTwiceArea += dCurrentX * dNextY - dNextX * dCurrentY; dMinimumX = qMin(dMinimumX, oCurrent.x()); dMaximumX = qMax(dMaximumX, oCurrent.x()); dMinimumY = qMin(dMinimumY, oCurrent.y()); dMaximumY = qMax(dMaximumY, oCurrent.y()); } const double dScale = qMax(1.0, qMax(dMaximumX - dMinimumX, dMaximumY - dMinimumY)); return vtkMath::IsFinite(dTwiceArea) && std::fabs(dTwiceArea) > 1.0e-12 * dScale * dScale; } } nmVTKPlanarPolygonInteractorStyle:: nmVTKPlanarPolygonInteractorStyle() : m_bCompleted(false) { } nmVTKPlanarPolygonInteractorStyle:: ~nmVTKPlanarPolygonInteractorStyle() { } void nmVTKPlanarPolygonInteractorStyle::OnLeftButtonDown() { if(m_bCompleted || this->Interactor == nullptr) { return; } // 单击追加顶点,VTK 将双击的第二次按下标记为 RepeatCount > 0。 if(this->Interactor->GetRepeatCount() > 0) { completePolygon(); return; } appendCurrentPoint(); } void nmVTKPlanarPolygonInteractorStyle::OnRightButtonDown() { // 绘制模式下右键专用于取消,不传递给默认相机或右键菜单。 if(!m_bCompleted) { cancelPolygon(); } } void nmVTKPlanarPolygonInteractorStyle::OnKeyPress() { if(m_bCompleted || this->Interactor == nullptr) { return; } const char* pKeySym = this->Interactor->GetKeySym(); if(pKeySym == nullptr) { return; } // Enter 完成、Backspace/Delete 撤回末点、Escape 取消,均由业务状态机收口。 if(std::strcmp(pKeySym, "Return") == 0 || std::strcmp(pKeySym, "KP_Enter") == 0) { completePolygon(); } else if(std::strcmp(pKeySym, "BackSpace") == 0 || std::strcmp(pKeySym, "Delete") == 0) { removeLastPoint(); } else if(std::strcmp(pKeySym, "Escape") == 0) { cancelPolygon(); } } void nmVTKPlanarPolygonInteractorStyle::OnChar() { // 字符命令在绘制模式没有业务含义,不能触发基类的退出或相机复位。 } void nmVTKPlanarPolygonInteractorStyle::reset() { m_vecPoints.clear(); m_bCompleted = false; } QVector nmVTKPlanarPolygonInteractorStyle::getPoints() const { return m_vecPoints; } int nmVTKPlanarPolygonInteractorStyle::getPointCount() const { return m_vecPoints.size(); } bool nmVTKPlanarPolygonInteractorStyle::isCompleted() const { return m_bCompleted; } bool nmVTKPlanarPolygonInteractorStyle::projectEventPositionToPlane( QPointF& oPoint) { if(this->Interactor == nullptr) { return false; } int* pEventPosition = this->Interactor->GetEventPosition(); if(pEventPosition == nullptr) { return false; } this->FindPokedRenderer(pEventPosition[0], pEventPosition[1]); vtkRenderer* pRenderer = this->CurrentRenderer; if(pRenderer == nullptr) { return false; } // 将同一屏幕点的近、远裁剪面坐标连成射线,再与 z=0 网格平面求交。 double dNearPoint[3]; double dFarPoint[3]; if(!displayToWorld(pRenderer, pEventPosition[0], pEventPosition[1], 0.0, dNearPoint) || !displayToWorld(pRenderer, pEventPosition[0], pEventPosition[1], 1.0, dFarPoint)) { return false; } const double dDirectionZ = dFarPoint[2] - dNearPoint[2]; const double dRayScale = qMax(1.0, qMax(std::fabs(dNearPoint[2]), std::fabs(dFarPoint[2]))); if(std::fabs(dDirectionZ) <= PLANAR_POLYGON_RAY_EPSILON * dRayScale) { return false; } const double dParameter = -dNearPoint[2] / dDirectionZ; const QPointF oCandidate( dNearPoint[0] + dParameter * (dFarPoint[0] - dNearPoint[0]), dNearPoint[1] + dParameter * (dFarPoint[1] - dNearPoint[1])); if(!isFinitePoint(oCandidate)) { return false; } oPoint = oCandidate; return true; } bool nmVTKPlanarPolygonInteractorStyle::appendCurrentPoint() { QPointF oPoint; if(!projectEventPositionToPlane(oPoint) || (!m_vecPoints.isEmpty() && isSamePoint(m_vecPoints.last(), oPoint))) { return false; } // 连续重合点不入队,否则会生成零长边并干扰后续三角化。 m_vecPoints.append(oPoint); this->InvokeEvent(PolygonChangedEvent); renderInteractor(); return true; } bool nmVTKPlanarPolygonInteractorStyle::completePolygon() { if(m_bCompleted) { return true; } if(!hasNonZeroArea(m_vecPoints)) { this->InvokeEvent(PolygonInvalidEvent); return false; } // 先发变更事件绘制闭合边,再通知业务层离开 VTK 交互栈。 m_bCompleted = true; this->InvokeEvent(PolygonChangedEvent); renderInteractor(); this->InvokeEvent(PolygonCompletedEvent); return true; } void nmVTKPlanarPolygonInteractorStyle::removeLastPoint() { if(m_vecPoints.isEmpty()) { return; } m_vecPoints.remove(m_vecPoints.size() - 1); this->InvokeEvent(PolygonChangedEvent); renderInteractor(); } void nmVTKPlanarPolygonInteractorStyle::cancelPolygon() { this->InvokeEvent(PolygonCancelledEvent); } void nmVTKPlanarPolygonInteractorStyle::renderInteractor() { if(this->Interactor != nullptr) { this->Interactor->Render(); } }