#include "nmVTKPlanarScaleGridLayer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { const double PLANAR_AXIS_DEPTH_FACTOR = 0.00020; const double PLANAR_GRID_DEPTH_FACTOR = 0.00050; const double PLANAR_BACKGROUND_DEPTH_FACTOR = 0.00100; const double PLANAR_CAMERA_SIDE_TOLERANCE_FACTOR = 0.000001; const double PLANAR_EDGE_SWITCH_PIXEL_TOLERANCE = 2.0; const double PLANAR_TICK_LENGTH_FACTOR = 0.012; const int PLANAR_LABEL_PIXEL_GAP = 8; const int PLANAR_CORNER_LABEL_PIXEL_SHIFT = 12; const int PLANAR_TARGET_INTERVAL_COUNT = 6; bool isPlanarScaleGridGuiThread() { QCoreApplication* pApplication = QCoreApplication::instance(); return pApplication == nullptr || pApplication->thread() == QThread::currentThread(); } bool isValidPlanarBounds(double dXMinimum, double dXMaximum, double dYMinimum, double dYMaximum, double dPlaneZ) { return vtkMath::IsFinite(dXMinimum) && vtkMath::IsFinite(dXMaximum) && vtkMath::IsFinite(dYMinimum) && vtkMath::IsFinite(dYMaximum) && vtkMath::IsFinite(dPlaneZ) && dXMinimum < dXMaximum && dYMinimum < dYMaximum; } void appendLine(vtkPoints* pPoints, vtkCellArray* pLines, double dX1, double dY1, double dZ1, double dX2, double dY2, double dZ2) { if(pPoints == nullptr || pLines == nullptr) { return; } vtkIdType nPointIds[2]; nPointIds[0] = pPoints->InsertNextPoint(dX1, dY1, dZ1); nPointIds[1] = pPoints->InsertNextPoint(dX2, dY2, dZ2); pLines->InsertNextCell(2, nPointIds); } void applyLineData(vtkPolyData* pPolyData, vtkPoints* pPoints, vtkCellArray* pLines) { if(pPolyData == nullptr || pPoints == nullptr || pLines == nullptr) { return; } pPolyData->SetPoints(pPoints); pPolyData->SetLines(pLines); pPolyData->Modified(); } void applySurfaceData(vtkPolyData* pPolyData, vtkPoints* pPoints, vtkCellArray* pPolygons) { if(pPolyData == nullptr || pPoints == nullptr || pPolygons == nullptr) { return; } pPolyData->SetPoints(pPoints); pPolyData->SetPolys(pPolygons); pPolyData->Modified(); } void updatePolyDataZ(vtkPolyData* pPolyData, double dZ) { vtkPoints* pPoints = pPolyData != nullptr ? pPolyData->GetPoints() : nullptr; if(pPoints == nullptr) { return; } double dPoint[3]; const vtkIdType nPointCount = pPoints->GetNumberOfPoints(); for(vtkIdType nPoint = 0; nPoint < nPointCount; ++nPoint) { pPoints->GetPoint(nPoint, dPoint); dPoint[2] = dZ; pPoints->SetPoint(nPoint, dPoint); } pPoints->Modified(); pPolyData->Modified(); } void configureLineActor(vtkPolyData* pPolyData, vtkPolyDataMapper* pMapper, vtkActor* pActor, double dRed, double dGreen, double dBlue, float fLineWidth) { if(pPolyData == nullptr || pMapper == nullptr || pActor == nullptr) { return; } pMapper->SetInputData(pPolyData); pMapper->ScalarVisibilityOff(); pActor->SetMapper(pMapper); pActor->GetProperty()->SetColor(dRed, dGreen, dBlue); pActor->GetProperty()->SetLineWidth(fLineWidth); pActor->GetProperty()->SetOpacity(1.0); pActor->GetProperty()->LightingOff(); pActor->PickableOff(); } void configureBackgroundActor(vtkPolyData* pPolyData, vtkPolyDataMapper* pMapper, vtkActor* pActor) { if(pPolyData == nullptr || pMapper == nullptr || pActor == nullptr) { return; } pMapper->SetInputData(pPolyData); pMapper->ScalarVisibilityOff(); pActor->SetMapper(pMapper); pActor->GetProperty()->SetColor( 171.0 / 255.0, 232.0 / 255.0, 241.0 / 255.0); pActor->GetProperty()->SetOpacity(1.0); pActor->GetProperty()->SetRepresentationToSurface(); pActor->GetProperty()->EdgeVisibilityOff(); pActor->GetProperty()->LightingOff(); // 底板两面都需要可见,相机翻到 XY 平面另一侧时仍由深度关系遮挡。 pActor->GetProperty()->FrontfaceCullingOff(); pActor->GetProperty()->BackfaceCullingOff(); pActor->PickableOff(); } bool calculateNiceTicks(double dMinimum, double dMaximum, std::vector& vTicks, double& dStep) { vTicks.clear(); dStep = 0.0; const double dRange = dMaximum - dMinimum; if(!vtkMath::IsFinite(dRange) || dRange <= 0.0) { return false; } const double dRawStep = dRange / static_cast(PLANAR_TARGET_INTERVAL_COUNT); const double dMagnitude = std::pow( 10.0, std::floor(std::log10(dRawStep))); if(!vtkMath::IsFinite(dMagnitude) || dMagnitude <= 0.0) { return false; } const double dNormalizedStep = dRawStep / dMagnitude; double dNiceFactor = 10.0; if(dNormalizedStep <= 1.0) { dNiceFactor = 1.0; } else if(dNormalizedStep <= 2.0) { dNiceFactor = 2.0; } else if(dNormalizedStep <= 2.5) { dNiceFactor = 2.5; } else if(dNormalizedStep <= 5.0) { dNiceFactor = 5.0; } dStep = dNiceFactor * dMagnitude; if(!vtkMath::IsFinite(dStep) || dStep <= 0.0) { return false; } const double dTolerance = dStep * 0.00000001; const double dFirstTick = std::ceil((dMinimum - dTolerance) / dStep) * dStep; for(int nIndex = 0; nIndex < 128; ++nIndex) { double dValue = dFirstTick + static_cast(nIndex) * dStep; if(dValue > dMaximum + dTolerance) { break; } if(dValue >= dMinimum - dTolerance) { if(std::fabs(dValue) < dTolerance) { dValue = 0.0; } vTicks.push_back(dValue); } } return !vTicks.empty(); } QString formatTickValue(double dValue, double dStep) { const double dAbsoluteStep = std::fabs(dStep); if(std::fabs(dValue) < dAbsoluteStep * 0.00000001) { dValue = 0.0; } if(std::fabs(dValue) >= 1000000000.0) { return QString::number(dValue, 'g', 8); } int nDecimalCount = 0; double dScaledStep = dAbsoluteStep; while(nDecimalCount < 8 && std::fabs(dScaledStep - std::floor(dScaledStep + 0.5)) > 0.00000001) { dScaledStep *= 10.0; ++nDecimalCount; } return QString::number(dValue, 'f', nDecimalCount); } int roundedPixelOffset(double dValue) { return static_cast(dValue >= 0.0 ? std::floor(dValue + 0.5) : std::ceil(dValue - 0.5)); } } class nmVTKPlanarScaleGridLayerPrivate; /** * @brief 在 VTK 每次绘制组合 Prop 前更新相机相关轴边,不注册额外 Observer。 */ class vtkNmPlanarScaleGridProp : public vtkPropAssembly { public: static vtkNmPlanarScaleGridProp* New(); vtkTypeMacro(vtkNmPlanarScaleGridProp, vtkPropAssembly); void setOwner(nmVTKPlanarScaleGridLayerPrivate* pOwner) { m_pOwner = pOwner; } virtual int RenderOpaqueGeometry(vtkViewport* pViewport); virtual int RenderOverlay(vtkViewport* pViewport); protected: vtkNmPlanarScaleGridProp() : m_pOwner(nullptr) { } virtual ~vtkNmPlanarScaleGridProp() { m_pOwner = nullptr; } private: vtkNmPlanarScaleGridProp(const vtkNmPlanarScaleGridProp&); void operator=(const vtkNmPlanarScaleGridProp&); nmVTKPlanarScaleGridLayerPrivate* m_pOwner; ///< 非所有权指针。 }; vtkStandardNewMacro(vtkNmPlanarScaleGridProp); class nmVTKPlanarScaleGridLayerPrivate { public: nmVTKPlanarScaleGridLayerPrivate() : m_pRootProp(vtkSmartPointer::New()), m_pBackgroundData(vtkSmartPointer::New()), m_pGridData(vtkSmartPointer::New()), m_pFrameData(vtkSmartPointer::New()), m_pXAxisData(vtkSmartPointer::New()), m_pYAxisData(vtkSmartPointer::New()), m_pBackgroundMapper(vtkSmartPointer::New()), m_pGridMapper(vtkSmartPointer::New()), m_pFrameMapper(vtkSmartPointer::New()), m_pXAxisMapper(vtkSmartPointer::New()), m_pYAxisMapper(vtkSmartPointer::New()), m_pBackgroundActor(vtkSmartPointer::New()), m_pGridActor(vtkSmartPointer::New()), m_pFrameActor(vtkSmartPointer::New()), m_pXAxisActor(vtkSmartPointer::New()), m_pYAxisActor(vtkSmartPointer::New()), m_pLabelTextProperty(vtkSmartPointer::New()), m_dPlaneZ(0.0), m_dScale(0.0), m_dBackgroundZ(0.0), m_dGridZ(0.0), m_dAxisZ(0.0), m_dTickLength(0.0), m_dXTickStep(0.0), m_dYTickStep(0.0), m_bHasBounds(false), m_bAxesInitialized(false), m_bXAxisAtMaximum(false), m_bYAxisAtMaximum(false), m_nCameraSide(1) { for(int nIndex = 0; nIndex < 4; ++nIndex) { m_dBounds[nIndex] = 0.0; } configureBackgroundActor(m_pBackgroundData, m_pBackgroundMapper, m_pBackgroundActor); configureLineActor(m_pGridData, m_pGridMapper, m_pGridActor, 25.0 / 255.0, 25.0 / 255.0, 112.0 / 255.0, 1.0f); // 先使用 VTK 7.1 原生线型;不支持线型的后端会自然回退为实线。 m_pGridActor->GetProperty()->SetLineStipplePattern(0xF0F0); m_pGridActor->GetProperty()->SetLineStippleRepeatFactor(1); configureLineActor(m_pFrameData, m_pFrameMapper, m_pFrameActor, 25.0 / 255.0, 25.0 / 255.0, 112.0 / 255.0, 1.5f); m_pFrameActor->GetProperty()->SetLineStipplePattern(0xFFFF); m_pFrameActor->GetProperty()->SetLineStippleRepeatFactor(1); configureLineActor(m_pXAxisData, m_pXAxisMapper, m_pXAxisActor, 1.0, 0.0, 0.0, 1.5f); configureLineActor(m_pYAxisData, m_pYAxisMapper, m_pYAxisActor, 0.0, 160.0 / 255.0, 0.0, 1.5f); m_pLabelTextProperty->SetFontFamilyToArial(); m_pLabelTextProperty->SetFontSize(14); m_pLabelTextProperty->SetColor( 235.0 / 255.0, 235.0 / 255.0, 190.0 / 255.0); m_pLabelTextProperty->SetBold(0); m_pLabelTextProperty->SetItalic(0); // VTK 7.1 根据浅色文字自动计算黑色对比阴影,没有 SetShadowColor 接口。 m_pLabelTextProperty->SetShadow(1); m_pLabelTextProperty->SetShadowOffset(1, -1); m_pLabelTextProperty->SetBackgroundOpacity(0.0); m_pLabelTextProperty->SetJustificationToCentered(); m_pLabelTextProperty->SetVerticalJustificationToCentered(); // RootProp 是 Scene 唯一接管的主 Prop,其余 Actor 生命周期均归本图层。 m_pRootProp->setOwner(this); m_pRootProp->AddPart(m_pBackgroundActor); m_pRootProp->AddPart(m_pGridActor); m_pRootProp->AddPart(m_pFrameActor); m_pRootProp->AddPart(m_pXAxisActor); m_pRootProp->AddPart(m_pYAxisActor); m_pRootProp->PickableOff(); } ~nmVTKPlanarScaleGridLayerPrivate() { // 先断开逐帧回调目标,再从组合 Prop 移除其持有的所有子 Prop 引用。 m_pRootProp->setOwner(nullptr); clearLabels(); m_pRootProp->RemovePart(m_pBackgroundActor); m_pRootProp->RemovePart(m_pGridActor); m_pRootProp->RemovePart(m_pFrameActor); m_pRootProp->RemovePart(m_pXAxisActor); m_pRootProp->RemovePart(m_pYAxisActor); } vtkProp* getRootProp() const { return m_pRootProp; } bool setBounds(double dXMinimum, double dXMaximum, double dYMinimum, double dYMaximum, double dPlaneZ) { std::vector vXTicks; std::vector vYTicks; double dXTickStep = 0.0; double dYTickStep = 0.0; if(!isValidPlanarBounds(dXMinimum, dXMaximum, dYMinimum, dYMaximum, dPlaneZ) || !calculateNiceTicks(dXMinimum, dXMaximum, vXTicks, dXTickStep) || !calculateNiceTicks(dYMinimum, dYMaximum, vYTicks, dYTickStep)) { return false; } m_dBounds[0] = dXMinimum; m_dBounds[1] = dXMaximum; m_dBounds[2] = dYMinimum; m_dBounds[3] = dYMaximum; m_vXTicks = vXTicks; m_vYTicks = vYTicks; m_dXTickStep = dXTickStep; m_dYTickStep = dYTickStep; m_dPlaneZ = dPlaneZ; m_dScale = dXMaximum - dXMinimum; if(dYMaximum - dYMinimum > m_dScale) { m_dScale = dYMaximum - dYMinimum; } m_dTickLength = m_dScale * PLANAR_TICK_LENGTH_FACTOR; updateDepthCoordinates(); rebuildBackgroundGeometry(); rebuildGridGeometry(); rebuildLabels(); rebuildAxisGeometry(false, false); m_bHasBounds = true; return true; } bool getBounds(double dBounds[4]) const { if(dBounds == nullptr || !m_bHasBounds) { return false; } for(int nIndex = 0; nIndex < 4; ++nIndex) { dBounds[nIndex] = m_dBounds[nIndex]; } return true; } void updateForRenderer(vtkRenderer* pRenderer) { if(pRenderer == nullptr || !m_bHasBounds) { return; } // 仅当相机明确越过带容差的平面分界时翻转深度顺序,侧视附近沿用上次侧别。 const bool bDepthChanged = updateCameraSide(pRenderer); if(bDepthChanged) { updateGeometryDepth(); } double dCornerDisplay[4][3]; const double dCorners[4][3] = { { m_dBounds[0], m_dBounds[2], m_dPlaneZ }, { m_dBounds[1], m_dBounds[2], m_dPlaneZ }, { m_dBounds[1], m_dBounds[3], m_dPlaneZ }, { m_dBounds[0], m_dBounds[3], m_dPlaneZ } }; for(int nCorner = 0; nCorner < 4; ++nCorner) { if(!projectPoint(pRenderer, dCorners[nCorner], dCornerDisplay[nCorner])) { return; } } const double dMinimumYAverage = (dCornerDisplay[0][1] + dCornerDisplay[1][1]) * 0.5; const double dMaximumYAverage = (dCornerDisplay[2][1] + dCornerDisplay[3][1]) * 0.5; const double dMinimumXAverage = (dCornerDisplay[0][0] + dCornerDisplay[3][0]) * 0.5; const double dMaximumXAverage = (dCornerDisplay[1][0] + dCornerDisplay[2][0]) * 0.5; // VTK 显示坐标原点位于左下角,平均值较小的边即当前底边或左边。 const bool bXAxisAtMaximum = selectMaximumEdge( dMinimumYAverage, dMaximumYAverage, m_bXAxisAtMaximum); const bool bYAxisAtMaximum = selectMaximumEdge( dMinimumXAverage, dMaximumXAverage, m_bYAxisAtMaximum); if(!m_bAxesInitialized || bXAxisAtMaximum != m_bXAxisAtMaximum || bYAxisAtMaximum != m_bYAxisAtMaximum) { rebuildAxisGeometry(bXAxisAtMaximum, bYAxisAtMaximum); } updateLabelPositions(pRenderer, bXAxisAtMaximum, bYAxisAtMaximum); } private: void updateDepthCoordinates() { const double dCameraSide = static_cast(m_nCameraSide); m_dAxisZ = m_dPlaneZ + dCameraSide * m_dScale * PLANAR_AXIS_DEPTH_FACTOR; m_dGridZ = m_dPlaneZ - dCameraSide * m_dScale * PLANAR_GRID_DEPTH_FACTOR; m_dBackgroundZ = m_dPlaneZ - dCameraSide * m_dScale * PLANAR_BACKGROUND_DEPTH_FACTOR; } bool updateCameraSide(vtkRenderer* pRenderer) { vtkCamera* pCamera = pRenderer != nullptr ? pRenderer->GetActiveCamera() : nullptr; const double* pCameraPosition = pCamera != nullptr ? pCamera->GetPosition() : nullptr; if(pCameraPosition == nullptr || !vtkMath::IsFinite(pCameraPosition[2])) { return false; } const double dDistanceToPlane = pCameraPosition[2] - m_dPlaneZ; const double dTolerance = m_dScale * PLANAR_CAMERA_SIDE_TOLERANCE_FACTOR; int nCameraSide = m_nCameraSide; if(dDistanceToPlane > dTolerance) { nCameraSide = 1; } else if(dDistanceToPlane < -dTolerance) { nCameraSide = -1; } if(nCameraSide == m_nCameraSide) { return false; } m_nCameraSide = nCameraSide; updateDepthCoordinates(); return true; } bool selectMaximumEdge(double dMinimumEdgeAverage, double dMaximumEdgeAverage, bool bCurrentMaximum) const { const double dDifference = dMaximumEdgeAverage - dMinimumEdgeAverage; if(dDifference < -PLANAR_EDGE_SWITCH_PIXEL_TOLERANCE) { return true; } if(dDifference > PLANAR_EDGE_SWITCH_PIXEL_TOLERANCE) { return false; } return m_bAxesInitialized ? bCurrentMaximum : false; } void updateGeometryDepth() { // 相机翻面只改已有点的 Z,不重建底板、比例线拓扑或任何 VTK Prop。 updatePolyDataZ(m_pBackgroundData, m_dBackgroundZ); updatePolyDataZ(m_pGridData, m_dGridZ); updatePolyDataZ(m_pFrameData, m_dAxisZ); updatePolyDataZ(m_pXAxisData, m_dAxisZ); updatePolyDataZ(m_pYAxisData, m_dAxisZ); } void rebuildBackgroundGeometry() { vtkSmartPointer pPoints = vtkSmartPointer::New(); vtkSmartPointer pPolygons = vtkSmartPointer::New(); pPoints->SetDataTypeToDouble(); vtkIdType nPointIds[4]; nPointIds[0] = pPoints->InsertNextPoint( m_dBounds[0], m_dBounds[2], m_dBackgroundZ); nPointIds[1] = pPoints->InsertNextPoint( m_dBounds[1], m_dBounds[2], m_dBackgroundZ); nPointIds[2] = pPoints->InsertNextPoint( m_dBounds[1], m_dBounds[3], m_dBackgroundZ); nPointIds[3] = pPoints->InsertNextPoint( m_dBounds[0], m_dBounds[3], m_dBackgroundZ); pPolygons->InsertNextCell(4, nPointIds); applySurfaceData(m_pBackgroundData, pPoints, pPolygons); } void rebuildGridGeometry() { vtkSmartPointer pPoints = vtkSmartPointer::New(); vtkSmartPointer pLines = vtkSmartPointer::New(); pPoints->SetDataTypeToDouble(); // 内部主刻度使用虚线;矩形外框由独立实线 Actor 绘制。 const double dXTolerance = m_dXTickStep * 0.00000001; for(std::size_t nIndex = 0; nIndex < m_vXTicks.size(); ++nIndex) { const double dX = m_vXTicks[nIndex]; if(dX > m_dBounds[0] + dXTolerance && dX < m_dBounds[1] - dXTolerance) { appendLine(pPoints, pLines, dX, m_dBounds[2], m_dGridZ, dX, m_dBounds[3], m_dGridZ); } } const double dYTolerance = m_dYTickStep * 0.00000001; for(std::size_t nIndex = 0; nIndex < m_vYTicks.size(); ++nIndex) { const double dY = m_vYTicks[nIndex]; if(dY > m_dBounds[2] + dYTolerance && dY < m_dBounds[3] - dYTolerance) { appendLine(pPoints, pLines, m_dBounds[0], dY, m_dGridZ, m_dBounds[1], dY, m_dGridZ); } } applyLineData(m_pGridData, pPoints, pLines); } void rebuildAxisGeometry(bool bXAxisAtMaximum, bool bYAxisAtMaximum) { const double dXAxisY = bXAxisAtMaximum ? m_dBounds[3] : m_dBounds[2]; const double dXAxisDirection = bXAxisAtMaximum ? 1.0 : -1.0; vtkSmartPointer pXPoints = vtkSmartPointer::New(); vtkSmartPointer pXLines = vtkSmartPointer::New(); pXPoints->SetDataTypeToDouble(); appendLine(pXPoints, pXLines, m_dBounds[0], dXAxisY, m_dAxisZ, m_dBounds[1], dXAxisY, m_dAxisZ); for(std::size_t nIndex = 0; nIndex < m_vXTicks.size(); ++nIndex) { appendLine(pXPoints, pXLines, m_vXTicks[nIndex], dXAxisY, m_dAxisZ, m_vXTicks[nIndex], dXAxisY + dXAxisDirection * m_dTickLength, m_dAxisZ); } applyLineData(m_pXAxisData, pXPoints, pXLines); const double dYAxisX = bYAxisAtMaximum ? m_dBounds[1] : m_dBounds[0]; const double dYAxisDirection = bYAxisAtMaximum ? 1.0 : -1.0; vtkSmartPointer pYPoints = vtkSmartPointer::New(); vtkSmartPointer pYLines = vtkSmartPointer::New(); pYPoints->SetDataTypeToDouble(); appendLine(pYPoints, pYLines, dYAxisX, m_dBounds[2], m_dAxisZ, dYAxisX, m_dBounds[3], m_dAxisZ); for(std::size_t nIndex = 0; nIndex < m_vYTicks.size(); ++nIndex) { appendLine(pYPoints, pYLines, dYAxisX, m_vYTicks[nIndex], m_dAxisZ, dYAxisX + dYAxisDirection * m_dTickLength, m_vYTicks[nIndex], m_dAxisZ); } applyLineData(m_pYAxisData, pYPoints, pYLines); // 外框只绘制当前坐标轴的两条对边,底边和左边分别由红、绿轴覆盖。 vtkSmartPointer pFramePoints = vtkSmartPointer::New(); vtkSmartPointer pFrameLines = vtkSmartPointer::New(); pFramePoints->SetDataTypeToDouble(); const double dOppositeXAxisY = bXAxisAtMaximum ? m_dBounds[2] : m_dBounds[3]; const double dOppositeYAxisX = bYAxisAtMaximum ? m_dBounds[0] : m_dBounds[1]; appendLine(pFramePoints, pFrameLines, m_dBounds[0], dOppositeXAxisY, m_dAxisZ, m_dBounds[1], dOppositeXAxisY, m_dAxisZ); appendLine(pFramePoints, pFrameLines, dOppositeYAxisX, m_dBounds[2], m_dAxisZ, dOppositeYAxisX, m_dBounds[3], m_dAxisZ); applyLineData(m_pFrameData, pFramePoints, pFrameLines); m_bXAxisAtMaximum = bXAxisAtMaximum; m_bYAxisAtMaximum = bYAxisAtMaximum; m_bAxesInitialized = true; } vtkSmartPointer createLabel( const QString& sText) { vtkSmartPointer pLabel = vtkSmartPointer::New(); const QByteArray aText = sText.toUtf8(); pLabel->SetInput(aText.constData()); pLabel->SetTextProperty(m_pLabelTextProperty); pLabel->SetTextScaleModeToNone(); pLabel->GetProperty()->SetDisplayLocationToForeground(); pLabel->PickableOff(); m_pRootProp->AddPart(pLabel); return pLabel; } void rebuildLabels() { clearLabels(); for(std::size_t nIndex = 0; nIndex < m_vXTicks.size(); ++nIndex) { m_vXLabels.push_back(createLabel( formatTickValue(m_vXTicks[nIndex], m_dXTickStep))); } for(std::size_t nIndex = 0; nIndex < m_vYTicks.size(); ++nIndex) { m_vYLabels.push_back(createLabel( formatTickValue(m_vYTicks[nIndex], m_dYTickStep))); } } void clearLabels() { for(std::size_t nIndex = 0; nIndex < m_vXLabels.size(); ++nIndex) { m_pRootProp->RemovePart(m_vXLabels[nIndex]); } for(std::size_t nIndex = 0; nIndex < m_vYLabels.size(); ++nIndex) { m_pRootProp->RemovePart(m_vYLabels[nIndex]); } m_vXLabels.clear(); m_vYLabels.clear(); } bool projectPoint(vtkRenderer* pRenderer, const double dWorld[3], double dDisplay[3]) const { if(pRenderer == nullptr || dWorld == nullptr || dDisplay == nullptr) { return false; } pRenderer->SetWorldPoint( dWorld[0], dWorld[1], dWorld[2], 1.0); pRenderer->WorldToDisplay(); const double* pDisplay = pRenderer->GetDisplayPoint(); if(pDisplay == nullptr || !vtkMath::IsFinite(pDisplay[0]) || !vtkMath::IsFinite(pDisplay[1]) || !vtkMath::IsFinite(pDisplay[2])) { return false; } dDisplay[0] = pDisplay[0]; dDisplay[1] = pDisplay[1]; dDisplay[2] = pDisplay[2]; return true; } bool calculateDisplayDirection(vtkRenderer* pRenderer, const double dAnchor[3], const double dOuterPoint[3], double dDirection[2]) const { double dAnchorDisplay[3]; double dOuterDisplay[3]; if(!projectPoint(pRenderer, dAnchor, dAnchorDisplay) || !projectPoint(pRenderer, dOuterPoint, dOuterDisplay)) { return false; } const double dX = dOuterDisplay[0] - dAnchorDisplay[0]; const double dY = dOuterDisplay[1] - dAnchorDisplay[1]; const double dLength = std::sqrt(dX * dX + dY * dY); if(!vtkMath::IsFinite(dLength) || dLength < 0.000001) { return false; } dDirection[0] = dX / dLength; dDirection[1] = dY / dLength; return true; } void updateLabelPositions(vtkRenderer* pRenderer, bool bXAxisAtMaximum, bool bYAxisAtMaximum) { const double dXAxisY = bXAxisAtMaximum ? m_dBounds[3] : m_dBounds[2]; const double dXAxisDirection = bXAxisAtMaximum ? 1.0 : -1.0; const double dXAxisAnchor[3] = { (m_dBounds[0] + m_dBounds[1]) * 0.5, dXAxisY, m_dAxisZ }; const double dXAxisOuterPoint[3] = { dXAxisAnchor[0], dXAxisY + dXAxisDirection * m_dTickLength, m_dAxisZ }; double dXAxisDisplayDirection[2] = { 0.0, -1.0 }; calculateDisplayDirection(pRenderer, dXAxisAnchor, dXAxisOuterPoint, dXAxisDisplayDirection); const int nXAxisOffsetX = roundedPixelOffset( dXAxisDisplayDirection[0] * PLANAR_LABEL_PIXEL_GAP); const int nXAxisOffsetY = roundedPixelOffset( dXAxisDisplayDirection[1] * PLANAR_LABEL_PIXEL_GAP); const double dXAxisMinimumPoint[3] = { m_dBounds[0], dXAxisY, m_dAxisZ }; const double dXAxisMaximumPoint[3] = { m_dBounds[1], dXAxisY, m_dAxisZ }; double dXAxisPositiveDirection[2] = { 1.0, 0.0 }; calculateDisplayDirection(pRenderer, dXAxisMinimumPoint, dXAxisMaximumPoint, dXAxisPositiveDirection); const double dXAxisCornerValue = bYAxisAtMaximum ? m_dBounds[1] : m_dBounds[0]; const double dXAxisCornerDirection = bYAxisAtMaximum ? -1.0 : 1.0; const double dXAxisTolerance = m_dXTickStep * 0.00000001; for(std::size_t nIndex = 0; nIndex < m_vXLabels.size(); ++nIndex) { const double dLabelWorld[3] = { m_vXTicks[nIndex], dXAxisOuterPoint[1], m_dAxisZ }; double dLabelDisplay[3]; if(!projectPoint(pRenderer, dLabelWorld, dLabelDisplay)) { m_vXLabels[nIndex]->SetVisibility(0); continue; } int nOffsetX = nXAxisOffsetX; int nOffsetY = nXAxisOffsetY; if(std::fabs(m_vXTicks[nIndex] - dXAxisCornerValue) <= dXAxisTolerance) { nOffsetX += roundedPixelOffset( dXAxisPositiveDirection[0] * dXAxisCornerDirection * PLANAR_CORNER_LABEL_PIXEL_SHIFT); nOffsetY += roundedPixelOffset( dXAxisPositiveDirection[1] * dXAxisCornerDirection * PLANAR_CORNER_LABEL_PIXEL_SHIFT); } m_vXLabels[nIndex]->SetDisplayPosition( roundedPixelOffset(dLabelDisplay[0]) + nOffsetX, roundedPixelOffset(dLabelDisplay[1]) + nOffsetY); m_vXLabels[nIndex]->SetVisibility(1); } const double dYAxisX = bYAxisAtMaximum ? m_dBounds[1] : m_dBounds[0]; const double dYAxisDirection = bYAxisAtMaximum ? 1.0 : -1.0; const double dYAxisAnchor[3] = { dYAxisX, (m_dBounds[2] + m_dBounds[3]) * 0.5, m_dAxisZ }; const double dYAxisOuterPoint[3] = { dYAxisX + dYAxisDirection * m_dTickLength, dYAxisAnchor[1], m_dAxisZ }; double dYAxisDisplayDirection[2] = { -1.0, 0.0 }; calculateDisplayDirection(pRenderer, dYAxisAnchor, dYAxisOuterPoint, dYAxisDisplayDirection); const int nYAxisOffsetX = roundedPixelOffset( dYAxisDisplayDirection[0] * PLANAR_LABEL_PIXEL_GAP); const int nYAxisOffsetY = roundedPixelOffset( dYAxisDisplayDirection[1] * PLANAR_LABEL_PIXEL_GAP); const double dYAxisMinimumPoint[3] = { dYAxisX, m_dBounds[2], m_dAxisZ }; const double dYAxisMaximumPoint[3] = { dYAxisX, m_dBounds[3], m_dAxisZ }; double dYAxisPositiveDirection[2] = { 0.0, 1.0 }; calculateDisplayDirection(pRenderer, dYAxisMinimumPoint, dYAxisMaximumPoint, dYAxisPositiveDirection); const double dYAxisCornerValue = bXAxisAtMaximum ? m_dBounds[3] : m_dBounds[2]; const double dYAxisCornerDirection = bXAxisAtMaximum ? -1.0 : 1.0; const double dYAxisTolerance = m_dYTickStep * 0.00000001; for(std::size_t nIndex = 0; nIndex < m_vYLabels.size(); ++nIndex) { const double dLabelWorld[3] = { dYAxisOuterPoint[0], m_vYTicks[nIndex], m_dAxisZ }; double dLabelDisplay[3]; if(!projectPoint(pRenderer, dLabelWorld, dLabelDisplay)) { m_vYLabels[nIndex]->SetVisibility(0); continue; } int nOffsetX = nYAxisOffsetX; int nOffsetY = nYAxisOffsetY; if(std::fabs(m_vYTicks[nIndex] - dYAxisCornerValue) <= dYAxisTolerance) { nOffsetX += roundedPixelOffset( dYAxisPositiveDirection[0] * dYAxisCornerDirection * PLANAR_CORNER_LABEL_PIXEL_SHIFT); nOffsetY += roundedPixelOffset( dYAxisPositiveDirection[1] * dYAxisCornerDirection * PLANAR_CORNER_LABEL_PIXEL_SHIFT); } m_vYLabels[nIndex]->SetDisplayPosition( roundedPixelOffset(dLabelDisplay[0]) + nOffsetX, roundedPixelOffset(dLabelDisplay[1]) + nOffsetY); m_vYLabels[nIndex]->SetVisibility(1); } } vtkSmartPointer m_pRootProp; vtkSmartPointer m_pBackgroundData; vtkSmartPointer m_pGridData; vtkSmartPointer m_pFrameData; vtkSmartPointer m_pXAxisData; vtkSmartPointer m_pYAxisData; vtkSmartPointer m_pBackgroundMapper; vtkSmartPointer m_pGridMapper; vtkSmartPointer m_pFrameMapper; vtkSmartPointer m_pXAxisMapper; vtkSmartPointer m_pYAxisMapper; vtkSmartPointer m_pBackgroundActor; vtkSmartPointer m_pGridActor; vtkSmartPointer m_pFrameActor; vtkSmartPointer m_pXAxisActor; vtkSmartPointer m_pYAxisActor; vtkSmartPointer m_pLabelTextProperty; std::vector > m_vXLabels; std::vector > m_vYLabels; std::vector m_vXTicks; std::vector m_vYTicks; double m_dBounds[4]; double m_dPlaneZ; double m_dScale; double m_dBackgroundZ; double m_dGridZ; double m_dAxisZ; double m_dTickLength; double m_dXTickStep; double m_dYTickStep; bool m_bHasBounds; bool m_bAxesInitialized; bool m_bXAxisAtMaximum; bool m_bYAxisAtMaximum; int m_nCameraSide; }; int vtkNmPlanarScaleGridProp::RenderOpaqueGeometry(vtkViewport* pViewport) { if(m_pOwner != nullptr) { m_pOwner->updateForRenderer(vtkRenderer::SafeDownCast(pViewport)); } return this->Superclass::RenderOpaqueGeometry(pViewport); } int vtkNmPlanarScaleGridProp::RenderOverlay(vtkViewport* pViewport) { if(m_pOwner != nullptr) { // Overlay 阶段再次同步屏幕坐标,确保文字使用本帧最终相机矩阵。 m_pOwner->updateForRenderer(vtkRenderer::SafeDownCast(pViewport)); } return this->Superclass::RenderOverlay(pViewport); } nmVTKPlanarScaleGridLayer::nmVTKPlanarScaleGridLayer( const QString& sLayerId) : nmVTKRenderLayer(sLayerId), m_pPrivate(new nmVTKPlanarScaleGridLayerPrivate) { Q_ASSERT(isPlanarScaleGridGuiThread()); setRenderProp(m_pPrivate->getRootProp()); } nmVTKPlanarScaleGridLayer::~nmVTKPlanarScaleGridLayer() { Q_ASSERT(isPlanarScaleGridGuiThread()); // 先从 Renderer 移除组合 Prop,再清除其逐帧更新目标和子 Prop 引用。 detach(); delete m_pPrivate; m_pPrivate = nullptr; } bool nmVTKPlanarScaleGridLayer::setBounds( double dXMinimum, double dXMaximum, double dYMinimum, double dYMaximum, double dPlaneZ) { Q_ASSERT(isPlanarScaleGridGuiThread()); return isPlanarScaleGridGuiThread() && m_pPrivate != nullptr && m_pPrivate->setBounds(dXMinimum, dXMaximum, dYMinimum, dYMaximum, dPlaneZ); } bool nmVTKPlanarScaleGridLayer::getBounds(double dBounds[4]) const { return m_pPrivate != nullptr && m_pPrivate->getBounds(dBounds); }