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