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/nmVTKScalarBarLayer.cpp

371 lines
13 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 "nmVTKScalarBarLayer.h"
#include <QByteArray>
#include <QCoreApplication>
#include <QThread>
#include <vtkActor2D.h>
#include <vtkCellArray.h>
#include <vtkCoordinate.h>
#include <vtkLookupTable.h>
#include <vtkMath.h>
#include <vtkPoints.h>
#include <vtkPolyData.h>
#include <vtkPolyDataMapper2D.h>
#include <vtkPropAssembly.h>
#include <vtkProperty2D.h>
#include <vtkScalarBarActor.h>
#include <vtkTextActor.h>
#include <vtkTextProperty.h>
namespace {
// ScalarBarActor 和 LookupTable 均只能在 Qt GUI 线程中修改。
bool isScalarBarGuiThread()
{
QCoreApplication* pApplication = QCoreApplication::instance();
return pApplication == nullptr ||
pApplication->thread() == QThread::currentThread();
}
// 独立色块使用归一化视口坐标,避免依赖仅支持 IndexedLookup 的 ScalarBar Annotation。
vtkSmartPointer<vtkActor2D> createRangeSwatchActor(vtkPoints* pPoints)
{
vtkSmartPointer<vtkCellArray> pPolygons =
vtkSmartPointer<vtkCellArray>::New();
pPolygons->InsertNextCell(4);
pPolygons->InsertCellPoint(0);
pPolygons->InsertCellPoint(1);
pPolygons->InsertCellPoint(2);
pPolygons->InsertCellPoint(3);
vtkSmartPointer<vtkPolyData> pData =
vtkSmartPointer<vtkPolyData>::New();
pData->SetPoints(pPoints);
pData->SetPolys(pPolygons);
vtkSmartPointer<vtkCoordinate> pViewportCoordinate =
vtkSmartPointer<vtkCoordinate>::New();
pViewportCoordinate->SetCoordinateSystemToNormalizedViewport();
vtkSmartPointer<vtkPolyDataMapper2D> pMapper =
vtkSmartPointer<vtkPolyDataMapper2D>::New();
pMapper->SetInputData(pData);
pMapper->SetTransformCoordinate(pViewportCoordinate);
pMapper->ScalarVisibilityOff();
vtkSmartPointer<vtkActor2D> pActor =
vtkSmartPointer<vtkActor2D>::New();
pActor->SetMapper(pMapper);
pActor->GetProperty()->SetDisplayLocationToForeground();
pActor->PickableOff();
pActor->SetVisibility(0);
return pActor;
}
// 统一独立文本样式,使常量值和越界说明与普通色标刻度保持一致。
void configureOverlayTextActor(vtkTextActor* pActor)
{
pActor->SetInput("");
pActor->SetTextScaleModeToNone();
pActor->GetTextProperty()->SetFontSize(11);
pActor->GetTextProperty()->BoldOff();
pActor->GetTextProperty()->ItalicOff();
pActor->GetTextProperty()->ShadowOff();
pActor->GetTextProperty()->SetJustificationToLeft();
pActor->GetTextProperty()->SetVerticalJustificationToCentered();
pActor->GetPositionCoordinate()->SetCoordinateSystemToNormalizedViewport();
pActor->PickableOff();
pActor->SetVisibility(0);
}
// 更新矩形四角并显式标记 Points 已修改,Mapper 会在下一次 Render 使用新位置。
void updateRectangle(vtkPoints* pPoints,
double dX, double dY,
double dWidth, double dHeight)
{
pPoints->SetPoint(0, dX, dY, 0.0);
pPoints->SetPoint(1, dX + dWidth, dY, 0.0);
pPoints->SetPoint(2, dX + dWidth, dY + dHeight, 0.0);
pPoints->SetPoint(3, dX, dY + dHeight, 0.0);
pPoints->Modified();
}
}
nmVTKScalarBarLayer::nmVTKScalarBarLayer(const QString& sLayerId)
: nmVTKRenderLayer(sLayerId),
m_pLookupTable(nullptr),
m_pRootAssembly(vtkSmartPointer<vtkPropAssembly>::New()),
m_pActor(vtkSmartPointer<vtkScalarBarActor>::New()),
m_pConstantValueActor(vtkSmartPointer<vtkTextActor>::New()),
m_pBelowRangeSwatchPoints(vtkSmartPointer<vtkPoints>::New()),
m_pBelowRangeSwatchActor(nullptr),
m_pBelowRangeTextActor(vtkSmartPointer<vtkTextActor>::New()),
m_pAboveRangeSwatchPoints(vtkSmartPointer<vtkPoints>::New()),
m_pAboveRangeSwatchActor(nullptr),
m_pAboveRangeTextActor(vtkSmartPointer<vtkTextActor>::New()),
m_sLabelFormat(QString::fromLatin1("%.6g")),
m_dPositionX(0.0),
m_dPositionY(0.0),
m_dWidth(0.1),
m_dHeight(0.8)
{
Q_ASSERT(isScalarBarGuiThread());
// vtkScalarBarActor 是二维 Prop,仍通过图层基类统一完成挂载和显隐。
m_pActor->SetNumberOfLabels(5);
m_pActor->SetLabelFormat("%.6g");
m_pActor->UnconstrainedFontSizeOn();
m_pActor->GetLabelTextProperty()->SetFontSize(11);
m_pActor->GetLabelTextProperty()->BoldOff();
m_pActor->GetLabelTextProperty()->ItalicOff();
m_pActor->GetLabelTextProperty()->ShadowOff();
m_pActor->GetTitleTextProperty()->SetFontSize(12);
m_pActor->GetTitleTextProperty()->BoldOff();
m_pActor->GetTitleTextProperty()->ItalicOff();
m_pActor->GetTitleTextProperty()->ShadowOff();
m_pActor->DrawAnnotationsOff();
m_pActor->DrawBelowRangeSwatchOff();
m_pActor->DrawAboveRangeSwatchOff();
// 这些二维装饰必须独立显示,不能为网格共享 LUT 开启 IndexedLookup。
m_pBelowRangeSwatchPoints->SetNumberOfPoints(4);
m_pAboveRangeSwatchPoints->SetNumberOfPoints(4);
m_pBelowRangeSwatchActor =
createRangeSwatchActor(m_pBelowRangeSwatchPoints);
m_pAboveRangeSwatchActor =
createRangeSwatchActor(m_pAboveRangeSwatchPoints);
configureOverlayTextActor(m_pConstantValueActor);
configureOverlayTextActor(m_pBelowRangeTextActor);
configureOverlayTextActor(m_pAboveRangeTextActor);
updateOverlayLayout();
// 根 Assembly 只在构造时注册,运行期仅更新子 Actor 状态。
m_pRootAssembly->AddPart(m_pActor);
m_pRootAssembly->AddPart(m_pConstantValueActor);
m_pRootAssembly->AddPart(m_pBelowRangeSwatchActor);
m_pRootAssembly->AddPart(m_pBelowRangeTextActor);
m_pRootAssembly->AddPart(m_pAboveRangeSwatchActor);
m_pRootAssembly->AddPart(m_pAboveRangeTextActor);
setRenderProp(m_pRootAssembly);
}
nmVTKScalarBarLayer::~nmVTKScalarBarLayer()
{
Q_ASSERT(isScalarBarGuiThread());
// 先移除二维 Prop,再释放对外部 LookupTable 的共享引用。
detach();
m_pLookupTable = nullptr;
m_pAboveRangeTextActor = nullptr;
m_pAboveRangeSwatchActor = nullptr;
m_pAboveRangeSwatchPoints = nullptr;
m_pBelowRangeTextActor = nullptr;
m_pBelowRangeSwatchActor = nullptr;
m_pBelowRangeSwatchPoints = nullptr;
m_pConstantValueActor = nullptr;
m_pActor = nullptr;
m_pRootAssembly = nullptr;
}
void nmVTKScalarBarLayer::setLookupTable(vtkLookupTable* pLookupTable)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread()) {
return;
}
// 智能指针共享 LookupTable,保证外部释放原指针后色标仍可安全显示。
m_pLookupTable = pLookupTable;
m_pActor->SetLookupTable(pLookupTable);
updateRangeSwatchColors();
}
vtkLookupTable* nmVTKScalarBarLayer::getLookupTable() const
{
return m_pLookupTable;
}
void nmVTKScalarBarLayer::setTitle(const QString& sTitle)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread()) {
return;
}
m_sTitle = sTitle;
// VTK 7.1 接口接收窄字符串,统一以 UTF-8 临时缓冲传入并由 Actor 复制。
const QByteArray aTitle = sTitle.toUtf8();
m_pActor->SetTitle(aTitle.constData());
}
QString nmVTKScalarBarLayer::getTitle() const
{
return m_sTitle;
}
bool nmVTKScalarBarLayer::setLabelFormat(const QString& sFormat)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread() || sFormat.isEmpty()) {
return false;
}
m_sLabelFormat = sFormat;
const QByteArray aFormat = sFormat.toLatin1();
m_pActor->SetLabelFormat(aFormat.constData());
return true;
}
QString nmVTKScalarBarLayer::getLabelFormat() const
{
return m_sLabelFormat;
}
bool nmVTKScalarBarLayer::setNumberOfLabels(int nLabelCount)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread() || nLabelCount < 0 || nLabelCount > 64) {
return false;
}
m_pActor->SetNumberOfLabels(nLabelCount);
return true;
}
int nmVTKScalarBarLayer::getNumberOfLabels() const
{
return m_pActor != nullptr ? m_pActor->GetNumberOfLabels() : 0;
}
void nmVTKScalarBarLayer::setBelowRangeSwatch(
bool bVisible,
const QString& sText)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread()) {
return;
}
m_sBelowRangeText = bVisible ? sText : QString();
const QByteArray aText = m_sBelowRangeText.toUtf8();
m_pBelowRangeTextActor->SetInput(aText.constData());
m_pBelowRangeSwatchActor->SetVisibility(bVisible ? 1 : 0);
m_pBelowRangeTextActor->SetVisibility(
bVisible && !m_sBelowRangeText.isEmpty() ? 1 : 0);
}
void nmVTKScalarBarLayer::setAboveRangeSwatch(
bool bVisible,
const QString& sText)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread()) {
return;
}
m_sAboveRangeText = bVisible ? sText : QString();
const QByteArray aText = m_sAboveRangeText.toUtf8();
m_pAboveRangeTextActor->SetInput(aText.constData());
m_pAboveRangeSwatchActor->SetVisibility(bVisible ? 1 : 0);
m_pAboveRangeTextActor->SetVisibility(
bVisible && !m_sAboveRangeText.isEmpty() ? 1 : 0);
}
void nmVTKScalarBarLayer::setConstantValueLabel(
bool bVisible,
const QString& sText)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread()) {
return;
}
m_sConstantValueText = bVisible ? sText : QString();
const QByteArray aText = m_sConstantValueText.toUtf8();
m_pConstantValueActor->SetInput(aText.constData());
m_pConstantValueActor->SetVisibility(
bVisible && !m_sConstantValueText.isEmpty() ? 1 : 0);
}
bool nmVTKScalarBarLayer::setPosition(double dX, double dY)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread() || !vtkMath::IsFinite(dX) ||
!vtkMath::IsFinite(dY)) {
return false;
}
// vtkScalarBarActor 默认使用归一化视口坐标,位置值由上层布局策略决定。
m_dPositionX = dX;
m_dPositionY = dY;
m_pActor->SetPosition(dX, dY);
updateOverlayLayout();
return true;
}
bool nmVTKScalarBarLayer::setSize(double dWidth, double dHeight)
{
Q_ASSERT(isScalarBarGuiThread());
if(!isScalarBarGuiThread() || !vtkMath::IsFinite(dWidth) ||
!vtkMath::IsFinite(dHeight) || dWidth <= 0.0 || dHeight <= 0.0) {
return false;
}
// 这里只拒绝非正尺寸,不替业务层限制色标是否超出视口。
m_dWidth = dWidth;
m_dHeight = dHeight;
m_pActor->SetWidth(dWidth);
m_pActor->SetHeight(dHeight);
updateOverlayLayout();
return true;
}
void nmVTKScalarBarLayer::updateOverlayLayout()
{
// 常量值放在色带右侧的垂直中心,视觉位置与普通刻度标签一致。
const double dLabelX = m_dPositionX + m_dWidth + 0.01;
const double dLabelY = m_dPositionY + m_dHeight * 0.5;
m_pConstantValueActor->GetPositionCoordinate()->SetValue(
dLabelX, dLabelY, 0.0);
// 越界色块位于色带上下两端外侧,避免覆盖连续色带和普通刻度。
double dSwatchWidth = m_dWidth * 0.25;
if(dSwatchWidth < 0.012) {
dSwatchWidth = 0.012;
} else if(dSwatchWidth > 0.025) {
dSwatchWidth = 0.025;
}
const double dSwatchHeight = 0.018;
const double dSwatchGap = 0.012;
const double dTextGap = 0.008;
const double dBelowY =
m_dPositionY - dSwatchGap - dSwatchHeight;
const double dAboveY =
m_dPositionY + m_dHeight + dSwatchGap;
updateRectangle(m_pBelowRangeSwatchPoints,
m_dPositionX, dBelowY,
dSwatchWidth, dSwatchHeight);
updateRectangle(m_pAboveRangeSwatchPoints,
m_dPositionX, dAboveY,
dSwatchWidth, dSwatchHeight);
m_pBelowRangeTextActor->GetPositionCoordinate()->SetValue(
m_dPositionX + dSwatchWidth + dTextGap,
dBelowY + dSwatchHeight * 0.5, 0.0);
m_pAboveRangeTextActor->GetPositionCoordinate()->SetValue(
m_dPositionX + dSwatchWidth + dTextGap,
dAboveY + dSwatchHeight * 0.5, 0.0);
}
void nmVTKScalarBarLayer::updateRangeSwatchColors()
{
if(m_pLookupTable == nullptr) {
return;
}
// 读取 LookupTable 的业务颜色属性,不直接访问内部特殊表项索引。
const double* pBelowColor = m_pLookupTable->GetBelowRangeColor();
const double* pAboveColor = m_pLookupTable->GetAboveRangeColor();
m_pBelowRangeSwatchActor->GetProperty()->SetColor(
pBelowColor[0], pBelowColor[1], pBelowColor[2]);
m_pBelowRangeSwatchActor->GetProperty()->SetOpacity(pBelowColor[3]);
m_pAboveRangeSwatchActor->GetProperty()->SetColor(
pAboveColor[0], pAboveColor[1], pAboveColor[2]);
m_pAboveRangeSwatchActor->GetProperty()->SetOpacity(pAboveColor[3]);
}
vtkScalarBarActor* nmVTKScalarBarLayer::getActor() const
{
return m_pActor;
}