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++

#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);
}