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/nmSubWxs/nmVolumeSceneController.cpp

275 lines
14 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 "nmVolumeSceneController.h"
#include "nmVolumeDataRenderAdapter.h"
#include "nmWxVTKRenderContainerWidget.h"
#include "nmVTKBoundsGridLayer.h"
#include "nmVTKMeshLayer.h"
#include "nmVTKScalarBarLayer.h"
#include "nmVTKScene.h"
#include <QCoreApplication>
#include <QScopedPointer>
#include <QThread>
#include <QUuid>
#include <cmath>
#include <new>
#include <vtkActor.h>
#include <vtkCamera.h>
#include <vtkDataSetMapper.h>
#include <vtkLookupTable.h>
#include <vtkMath.h>
#include <vtkProperty.h>
#include <vtkRenderer.h>
namespace {
// 固定文本标记供 Qt 4 lupdate 提取;运行期通过统一上下文翻译。
static const char* const s_pVolumeTranslationMarkers[] = {
QT_TRANSLATE_NOOP("nmVolumeSceneController", " (Cell)"),
QT_TRANSLATE_NOOP("nmVolumeSceneController", " (Point)"),
QT_TRANSLATE_NOOP("nmVolumeSceneController", "Not enough memory to display the volume mesh."),
QT_TRANSLATE_NOOP("nmVolumeSceneController", "Unable to bind the volume scalar property.")
};
/** @brief Renderer 和 Mapper 只允许所属 GUI 线程操作。 */
bool isVolumeGuiThread()
{
return QCoreApplication::instance() == nullptr ||
QCoreApplication::instance()->thread() == QThread::currentThread();
}
/** @brief 返回与业务窗口解耦的三维场景翻译文本。 */
QString sceneText(const char* pText)
{
Q_UNUSED(s_pVolumeTranslationMarkers);
return QCoreApplication::translate("nmVolumeSceneController", pText);
}
}
class nmVolumeSceneControllerPrivate
{
public:
/** @brief 初始化未挂载图层的空场景,构造本身不改动画面。 */
explicit nmVolumeSceneControllerPrivate(nmWxVTKRenderContainerWidget* pContainer)
: m_pContainer(pContainer), m_pAdapter(nullptr), m_pMesh(nullptr),
m_pScalarBar(nullptr), m_pBounds(nullptr), m_bVisible(false),
m_bScalarBarVisible(true), m_bScaleGridVisible(false) {}
/** @brief 仅移除仍具有相同对象身份的图层,防止误删外部同 ID 图层。 */
~nmVolumeSceneControllerPrivate()
{
// Scene 拥有图层,先解除 Mapper 对适配器的引用,再释放数据。
nmVTKScene* pScene = m_pContainer == nullptr ? nullptr : m_pContainer->getScene();
if(pScene != nullptr) {
if(m_pMesh != nullptr && pScene->getLayer(m_sMeshId) == m_pMesh) pScene->removeLayer(m_sMeshId);
if(m_pScalarBar != nullptr && pScene->getLayer(m_sScalarBarId) == m_pScalarBar) pScene->removeLayer(m_sScalarBarId);
if(m_pBounds != nullptr && pScene->getLayer(m_sBoundsId) == m_pBounds) pScene->removeLayer(m_sBoundsId);
}
delete m_pAdapter;
}
/** @brief 通过 Scene 登记验证观察指针,绝不解引用被外部清理的图层。 */
bool isIntact() const
{
nmVTKScene* pScene = m_pContainer == nullptr ? nullptr : m_pContainer->getScene();
return !m_pData.isNull() && pScene != nullptr && m_pMesh != nullptr &&
m_pScalarBar != nullptr && m_pBounds != nullptr &&
pScene->getLayer(m_sMeshId) == m_pMesh &&
pScene->getLayer(m_sScalarBarId) == m_pScalarBar &&
pScene->getLayer(m_sBoundsId) == m_pBounds;
}
/** @brief 将模型可见状态与各覆盖层偏好合成,不触发 Render。 */
void updateVisibility()
{
if(!isIntact()) return;
m_pMesh->setVisible(m_bVisible);
m_pScalarBar->setVisible(m_bVisible && m_bScalarBarVisible && !m_sPropertyId.isEmpty());
m_pBounds->setVisible(m_bVisible && m_bScaleGridVisible);
}
/** @brief 完整验证候选 LUT 和属性后提交 Mapper,失败时保持旧绑定。 */
bool bindProperty(const QString& sPropertyId)
{
const nmVolumePropertyDescriptor* pProperty = m_pData->findProperty(sPropertyId);
if(pProperty == nullptr) return false;
double dMinimum = pProperty->m_dMinimum;
double dMaximum = pProperty->m_dMaximum;
const bool bConstant = dMinimum == dMaximum;
if(bConstant) {
// LUT 需要严格递增区间;色标仍显示真实常量值,不能伪造业务范围。
const double dPadding = qMax(1.0, std::fabs(dMinimum)) * 1e-6;
dMaximum += dPadding;
if(!vtkMath::IsFinite(dMaximum)) {
dMaximum = dMinimum;
dMinimum -= dPadding;
}
}
vtkSmartPointer<vtkLookupTable> pTable = vtkSmartPointer<vtkLookupTable>::New();
pTable->SetNumberOfTableValues(256);
pTable->SetHueRange(0.67, 0.0);
pTable->SetTableRange(dMinimum, dMaximum);
pTable->SetNanColor(0.65, 0.65, 0.65, 1.0);
pTable->Build();
const nmVTKMeshLayer::ScalarAssociation eAssociation =
pProperty->m_eAssociation == NM_VolumeProperty_CellData
? nmVTKMeshLayer::ScalarAssociation_CellData : nmVTKMeshLayer::ScalarAssociation_PointData;
if(!m_pMesh->setInputConnection(m_pAdapter->getOutputPort(), pProperty->m_sArrayName, eAssociation, pTable)) return false;
m_pMesh->setScalarVisibility(true);
QString sTitle = pProperty->m_sArrayName;
const QString sOtherId = (pProperty->m_eAssociation == NM_VolumeProperty_CellData
? QString::fromLatin1("point:") : QString::fromLatin1("cell:")) + sTitle;
if(m_pData->findProperty(sOtherId) != nullptr) {
sTitle += pProperty->m_eAssociation == NM_VolumeProperty_CellData
? sceneText(" (Cell)") : sceneText(" (Point)");
}
m_pScalarBar->setTitle(sTitle);
m_pScalarBar->setLookupTable(pTable);
m_pScalarBar->setConstantValueLabel(bConstant, QString::number(pProperty->m_dMinimum, 'g', 8));
m_pScalarBar->setNumberOfLabels(bConstant ? 0 : m_pScalarBar->getResponsiveLabelCount());
m_sPropertyId = sPropertyId;
return true;
}
nmWxVTKRenderContainerWidget* m_pContainer; ///< 非所有权,生命周期长于控制器。
QSharedPointer<const nmVolumeData> m_pData; ///< 当前完整只读三维数据强引用。
nmVolumeDataRenderAdapter* m_pAdapter; ///< 本状态独占的渲染管线适配器。
nmVTKMeshLayer* m_pMesh; ///< Scene 所有的主模型图层,仅作非所有权观察。
nmVTKScalarBarLayer* m_pScalarBar; ///< Scene 所有的色标图层。
nmVTKBoundsGridLayer* m_pBounds; ///< Scene 所有的三维坐标框图层。
QString m_sMeshId; ///< 主模型图层稳定 ID,带本次候选唯一前缀。
QString m_sScalarBarId; ///< 色标图层稳定 ID。
QString m_sBoundsId; ///< 三维坐标框图层稳定 ID。
QString m_sPropertyId; ///< 最后成功绑定的属性 ID,纯几何时为空。
bool m_bVisible; ///< 是否已由窗口事务激活,而非尚在准备的隐藏候选。
bool m_bScalarBarVisible; ///< 当前三维模型的独立色标偏好。
bool m_bScaleGridVisible; ///< 当前三维模型的独立坐标框偏好。
};
nmVolumeSceneController::nmVolumeSceneController(nmWxVTKRenderContainerWidget* pContainer)
: m_pPrivate(new nmVolumeSceneControllerPrivate(pContainer)) {}
nmVolumeSceneController::~nmVolumeSceneController()
{
if(m_pPrivate->m_bVisible && m_pPrivate->m_pContainer != nullptr) {
m_pPrivate->m_pContainer->setRotationCenterPickTarget(nullptr);
}
delete m_pPrivate;
}
bool nmVolumeSceneController::setData(const QSharedPointer<const nmVolumeData>& pData, QString* pError)
{
Q_ASSERT(isVolumeGuiThread());
if(pError != nullptr) pError->clear();
if(!isVolumeGuiThread() || pData.isNull() || m_pPrivate->m_pContainer == nullptr) return false;
try {
QScopedPointer<nmVolumeSceneControllerPrivate> pCandidate(new nmVolumeSceneControllerPrivate(m_pPrivate->m_pContainer));
pCandidate->m_pData = pData;
pCandidate->m_pAdapter = new nmVolumeDataRenderAdapter(pData);
// 每次候选使用独立 ID,旧场景直到候选全部准备成功才释放。
const QString sPrefix = QString::fromLatin1("nm.volume.") + QUuid::createUuid().toString();
pCandidate->m_sMeshId = sPrefix + QLatin1String(".mesh");
pCandidate->m_sScalarBarId = sPrefix + QLatin1String(".scalar");
pCandidate->m_sBoundsId = sPrefix + QLatin1String(".bounds");
nmVTKScene* pScene = pCandidate->m_pContainer->getScene();
if(pScene == nullptr) return false;
QScopedPointer<nmVTKMeshLayer> pMesh(new nmVTKMeshLayer(pCandidate->m_sMeshId));
pMesh->setVisible(false);
if(!pScene->addLayer(pMesh.data())) return false;
pCandidate->m_pMesh = pMesh.take();
QScopedPointer<nmVTKScalarBarLayer> pScalarBar(new nmVTKScalarBarLayer(pCandidate->m_sScalarBarId));
pScalarBar->setVisible(false);
if(!pScene->addLayer(pScalarBar.data())) return false;
pCandidate->m_pScalarBar = pScalarBar.take();
QScopedPointer<nmVTKBoundsGridLayer> pBounds(new nmVTKBoundsGridLayer(pCandidate->m_sBoundsId));
pBounds->setVisible(false);
if(!pScene->addLayer(pBounds.data())) return false;
pCandidate->m_pBounds = pBounds.take();
double dBounds[6];
pData->getBounds(dBounds);
pCandidate->m_pBounds->setCamera(pCandidate->m_pContainer->getRenderer()->GetActiveCamera());
pCandidate->m_pBounds->setMode(nmVTKBoundsGridLayer::Mode_Box3D);
if(!pCandidate->m_pBounds->setBounds(dBounds[0], dBounds[1], dBounds[2], dBounds[3], dBounds[4], dBounds[5])) return false;
pCandidate->m_pMesh->setDisplayMode(nmVTKMeshLayer::DisplayMode_SurfaceWithEdges);
pCandidate->m_pMesh->getActor()->GetProperty()->SetEdgeColor(0.0, 0.0, 0.0);
pCandidate->m_pMesh->getActor()->GetProperty()->SetColor(0.65, 0.65, 0.65);
if(pData->getInitialPropertyId().isEmpty()) {
if(!pCandidate->m_pMesh->setInputConnection(pCandidate->m_pAdapter->getOutputPort())) return false;
pCandidate->m_pMesh->setScalarVisibility(false);
} else if(!pCandidate->bindProperty(pData->getInitialPropertyId())) {
if(pError != nullptr) *pError = sceneText("Unable to bind the volume scalar property.");
return false;
}
const QSize oSize = pCandidate->m_pContainer->getViewportSize();
pCandidate->m_pScalarBar->setViewportSize(oSize.width(), oSize.height());
// 从此不再存在失败分支。先解除旧拾取引用,再销毁旧 Actor。
if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->setRotationCenterPickTarget(nullptr);
delete m_pPrivate;
m_pPrivate = pCandidate.take();
return true;
} catch(const std::bad_alloc&) {
if(pError != nullptr) *pError = sceneText("Not enough memory to display the volume mesh.");
return false;
}
}
void nmVolumeSceneController::activate(bool bFitCamera)
{
if(!hasData()) return;
double dBounds[6];
m_pPrivate->m_pData->getBounds(dBounds);
m_pPrivate->m_bVisible = true;
m_pPrivate->updateVisibility();
m_pPrivate->m_pContainer->setPrimaryViewBounds(dBounds);
m_pPrivate->m_pContainer->setRotationCenterPickTarget(m_pPrivate->m_pMesh->getActor());
if(bFitCamera) {
m_pPrivate->m_pContainer->fitIsometricViewWithoutRender();
m_pPrivate->m_pContainer->resetRotationCenterWithoutRender();
}
}
void nmVolumeSceneController::hide()
{
if(!isVolumeGuiThread()) return;
m_pPrivate->m_bVisible = false;
m_pPrivate->updateVisibility();
}
void nmVolumeSceneController::clear()
{
if(!isVolumeGuiThread()) return;
nmWxVTKRenderContainerWidget* pContainer = m_pPrivate->m_pContainer;
QScopedPointer<nmVolumeSceneControllerPrivate> pEmpty(new nmVolumeSceneControllerPrivate(pContainer));
if(m_pPrivate->m_bVisible) {
pContainer->setRotationCenterPickTarget(nullptr);
pContainer->clearPrimaryViewBounds();
}
delete m_pPrivate;
m_pPrivate = pEmpty.take();
}
bool nmVolumeSceneController::hasData() const { return isVolumeGuiThread() && m_pPrivate->isIntact(); }
bool nmVolumeSceneController::applyProperty(const QString& sPropertyId, QString* pError)
{
if(pError != nullptr) pError->clear();
if(!hasData()) return false;
if(sPropertyId == m_pPrivate->m_sPropertyId) return true;
if(!m_pPrivate->bindProperty(sPropertyId)) {
if(pError != nullptr) *pError = sceneText("Unable to bind the volume scalar property.");
return false;
}
m_pPrivate->updateVisibility();
if(m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView();
return true;
}
void nmVolumeSceneController::setViewportSize(int nWidth, int nHeight)
{
if(hasData()) m_pPrivate->m_pScalarBar->setViewportSize(nWidth, nHeight);
}
void nmVolumeSceneController::setScalarBarVisible(bool bVisible)
{
if(!isVolumeGuiThread()) return;
if(m_pPrivate->m_bScalarBarVisible == bVisible) return;
m_pPrivate->m_bScalarBarVisible = bVisible;
m_pPrivate->updateVisibility();
if(hasData() && m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView();
}
void nmVolumeSceneController::setScaleGridVisible(bool bVisible)
{
if(!isVolumeGuiThread()) return;
if(m_pPrivate->m_bScaleGridVisible == bVisible) return;
m_pPrivate->m_bScaleGridVisible = bVisible;
m_pPrivate->updateVisibility();
if(hasData() && m_pPrivate->m_bVisible) m_pPrivate->m_pContainer->renderView();
}
bool nmVolumeSceneController::isScalarBarVisible() const { return m_pPrivate->m_bScalarBarVisible; }
bool nmVolumeSceneController::isScaleGridVisible() const { return m_pPrivate->m_bScaleGridVisible; }
bool nmVolumeSceneController::hasScalarProperty() const { return hasData() && !m_pPrivate->m_sPropertyId.isEmpty(); }