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

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