You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
nmWTAI-Platform/Src/nmNum/nmRender/nmVTKPlanarScaleGridLayer.cpp

988 lines
36 KiB
C++

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

#include "nmVTKPlanarScaleGridLayer.h"
#include <QByteArray>
#include <QCoreApplication>
#include <QThread>
#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkCellArray.h>
#include <vtkMath.h>
#include <vtkObjectFactory.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper.h>
#include <vtkPropAssembly.h>
#include <vtkProperty.h>
#include <vtkProperty2D.h>
#include <vtkRenderer.h>
#include <vtkTextActor.h>
#include <vtkTextProperty.h>
#include <vtkViewport.h>
#include <cstddef>
#include <cmath>
#include <vector>
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<double>& 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<double>(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<double>(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<int>(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<vtkNmPlanarScaleGridProp>::New()),
m_pBackgroundData(vtkSmartPointer<vtkPolyData>::New()),
m_pGridData(vtkSmartPointer<vtkPolyData>::New()),
m_pFrameData(vtkSmartPointer<vtkPolyData>::New()),
m_pXAxisData(vtkSmartPointer<vtkPolyData>::New()),
m_pYAxisData(vtkSmartPointer<vtkPolyData>::New()),
m_pBackgroundMapper(vtkSmartPointer<vtkPolyDataMapper>::New()),
m_pGridMapper(vtkSmartPointer<vtkPolyDataMapper>::New()),
m_pFrameMapper(vtkSmartPointer<vtkPolyDataMapper>::New()),
m_pXAxisMapper(vtkSmartPointer<vtkPolyDataMapper>::New()),
m_pYAxisMapper(vtkSmartPointer<vtkPolyDataMapper>::New()),
m_pBackgroundActor(vtkSmartPointer<vtkActor>::New()),
m_pGridActor(vtkSmartPointer<vtkActor>::New()),
m_pFrameActor(vtkSmartPointer<vtkActor>::New()),
m_pXAxisActor(vtkSmartPointer<vtkActor>::New()),
m_pYAxisActor(vtkSmartPointer<vtkActor>::New()),
m_pLabelTextProperty(vtkSmartPointer<vtkTextProperty>::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<double> vXTicks;
std::vector<double> 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<double>(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<vtkPoints> pPoints =
vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkCellArray> pPolygons =
vtkSmartPointer<vtkCellArray>::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<vtkPoints> pPoints =
vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkCellArray> pLines =
vtkSmartPointer<vtkCellArray>::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<vtkPoints> pXPoints =
vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkCellArray> pXLines =
vtkSmartPointer<vtkCellArray>::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<vtkPoints> pYPoints =
vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkCellArray> pYLines =
vtkSmartPointer<vtkCellArray>::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<vtkPoints> pFramePoints =
vtkSmartPointer<vtkPoints>::New();
vtkSmartPointer<vtkCellArray> pFrameLines =
vtkSmartPointer<vtkCellArray>::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<vtkTextActor> createLabel(
const QString& sText)
{
vtkSmartPointer<vtkTextActor> pLabel =
vtkSmartPointer<vtkTextActor>::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<vtkNmPlanarScaleGridProp> m_pRootProp;
vtkSmartPointer<vtkPolyData> m_pBackgroundData;
vtkSmartPointer<vtkPolyData> m_pGridData;
vtkSmartPointer<vtkPolyData> m_pFrameData;
vtkSmartPointer<vtkPolyData> m_pXAxisData;
vtkSmartPointer<vtkPolyData> m_pYAxisData;
vtkSmartPointer<vtkPolyDataMapper> m_pBackgroundMapper;
vtkSmartPointer<vtkPolyDataMapper> m_pGridMapper;
vtkSmartPointer<vtkPolyDataMapper> m_pFrameMapper;
vtkSmartPointer<vtkPolyDataMapper> m_pXAxisMapper;
vtkSmartPointer<vtkPolyDataMapper> m_pYAxisMapper;
vtkSmartPointer<vtkActor> m_pBackgroundActor;
vtkSmartPointer<vtkActor> m_pGridActor;
vtkSmartPointer<vtkActor> m_pFrameActor;
vtkSmartPointer<vtkActor> m_pXAxisActor;
vtkSmartPointer<vtkActor> m_pYAxisActor;
vtkSmartPointer<vtkTextProperty> m_pLabelTextProperty;
std::vector<vtkSmartPointer<vtkTextActor> > m_vXLabels;
std::vector<vtkSmartPointer<vtkTextActor> > m_vYLabels;
std::vector<double> m_vXTicks;
std::vector<double> 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);
}