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

265 lines
7.4 KiB
C++

#include "nmVTKCameraController.h"
#include <QCoreApplication>
#include <QThread>
#include <vtkCamera.h>
#include <vtkMath.h>
#include <vtkRenderer.h>
namespace {
// 相机和 Renderer 都是界面对象Release 中也用返回值保护线程边界。
bool isCameraGuiThread()
{
QCoreApplication* pApplication = QCoreApplication::instance();
return pApplication == nullptr ||
pApplication->thread() == QThread::currentThread();
}
}
nmVTKCameraController::nmVTKCameraController(vtkRenderer* pRenderer)
: m_pRenderer(pRenderer)
{
Q_ASSERT(isCameraGuiThread());
}
nmVTKCameraController::~nmVTKCameraController()
{
Q_ASSERT(isCameraGuiThread());
// Renderer 为非所有权引用,析构只解除关系,不修改相机或场景。
m_pRenderer = nullptr;
}
void nmVTKCameraController::setRenderer(vtkRenderer* pRenderer)
{
Q_ASSERT(isCameraGuiThread());
if(isCameraGuiThread()) {
m_pRenderer = pRenderer;
}
}
vtkRenderer* nmVTKCameraController::getRenderer() const
{
return m_pRenderer;
}
bool nmVTKCameraController::resetCamera()
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
return false;
}
vtkCamera* pCamera = m_pRenderer->GetActiveCamera();
if(pCamera == nullptr) {
return false;
}
// 重置入口恢复默认三维透视投影,再按当前可见边界适配。
pCamera->ParallelProjectionOff();
m_pRenderer->ResetCamera(dBounds);
m_pRenderer->ResetCameraClippingRange(dBounds);
return true;
}
bool nmVTKCameraController::zoomIn()
{
return zoom(1.2);
}
bool nmVTKCameraController::zoomOut()
{
return zoom(1.0 / 1.2);
}
bool nmVTKCameraController::fitView()
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
return false;
}
vtkCamera* pCamera = m_pRenderer->GetActiveCamera();
if(pCamera == nullptr) {
return false;
}
// ResetCamera 只重新适配边界,不改写调用方选定的观察方向和投影方式。
m_pRenderer->ResetCamera(dBounds);
m_pRenderer->ResetCameraClippingRange(dBounds);
return true;
}
bool nmVTKCameraController::setTopView()
{
return applyStandardView(0.0, 0.0, 1.0,
0.0, 1.0, 0.0, true);
}
bool nmVTKCameraController::setFrontView()
{
return applyStandardView(0.0, -1.0, 0.0,
0.0, 0.0, 1.0, true);
}
bool nmVTKCameraController::setRightView()
{
return applyStandardView(1.0, 0.0, 0.0,
0.0, 0.0, 1.0, true);
}
bool nmVTKCameraController::setIsometricView()
{
return applyStandardView(1.0, -1.0, 1.0,
0.0, 0.0, 1.0, false);
}
bool nmVTKCameraController::setRotationCenter(
double dX,
double dY,
double dZ)
{
Q_ASSERT(isCameraGuiThread());
if(!isCameraGuiThread() || m_pRenderer == nullptr ||
!vtkMath::IsFinite(dX) || !vtkMath::IsFinite(dY) ||
!vtkMath::IsFinite(dZ)) {
return false;
}
vtkCamera* pCamera = m_pRenderer->GetActiveCamera();
if(pCamera == nullptr) {
return false;
}
double dOldFocalPoint[3];
double dOldPosition[3];
pCamera->GetFocalPoint(dOldFocalPoint);
pCamera->GetPosition(dOldPosition);
pCamera->SetFocalPoint(dX, dY, dZ);
// 同步平移相机位置,保持原观察方向和相机到焦点的距离。
pCamera->SetPosition(
dOldPosition[0] + dX - dOldFocalPoint[0],
dOldPosition[1] + dY - dOldFocalPoint[1],
dOldPosition[2] + dZ - dOldFocalPoint[2]);
m_pRenderer->ResetCameraClippingRange();
return true;
}
bool nmVTKCameraController::resetRotationCenter()
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
return false;
}
// 复用平移逻辑,保持当前观察方向、投影方式和观察距离不变。
return setRotationCenter((dBounds[0] + dBounds[1]) * 0.5,
(dBounds[2] + dBounds[3]) * 0.5,
(dBounds[4] + dBounds[5]) * 0.5);
}
bool nmVTKCameraController::getVisibleBounds(double dBounds[6]) const
{
if(m_pRenderer == nullptr || dBounds == nullptr) {
return false;
}
// 只使用可见 Prop隐藏图层不会影响标准视角和旋转中心。
m_pRenderer->ComputeVisiblePropBounds(dBounds);
if(!vtkMath::AreBoundsInitialized(dBounds)) {
return false;
}
for(int nIndex = 0; nIndex < 6; ++nIndex) {
if(!vtkMath::IsFinite(dBounds[nIndex])) {
return false;
}
}
return dBounds[0] <= dBounds[1] &&
dBounds[2] <= dBounds[3] &&
dBounds[4] <= dBounds[5];
}
bool nmVTKCameraController::applyStandardView(
double dDirectionX,
double dDirectionY,
double dDirectionZ,
double dViewUpX,
double dViewUpY,
double dViewUpZ,
bool bParallelProjection)
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
if(!isCameraGuiThread() || !getVisibleBounds(dBounds)) {
return false;
}
vtkCamera* pCamera = m_pRenderer->GetActiveCamera();
if(pCamera == nullptr) {
return false;
}
double dDirection[3] = {
dDirectionX, dDirectionY, dDirectionZ
};
if(vtkMath::Normalize(dDirection) == 0.0) {
return false;
}
const double dCenter[3] = {
(dBounds[0] + dBounds[1]) * 0.5,
(dBounds[2] + dBounds[3]) * 0.5,
(dBounds[4] + dBounds[5]) * 0.5
};
// 先给相机一个与最大跨度成比例的稳定距离,再由 ResetCamera 精确适配。
const double dExtent = qMax(
qMax(dBounds[1] - dBounds[0],
dBounds[3] - dBounds[2]),
dBounds[5] - dBounds[4]);
const double dDistance = dExtent > 0.0 ? dExtent * 2.0 : 1.0;
// 顶、前、右视图分别位于 +Z、-Y、+X 一侧观察包围盒中心。
// 等轴视图使用透视投影,三个标准正视图使用正交投影。
pCamera->SetFocalPoint(dCenter);
pCamera->SetPosition(dCenter[0] + dDirection[0] * dDistance,
dCenter[1] + dDirection[1] * dDistance,
dCenter[2] + dDirection[2] * dDistance);
pCamera->SetViewUp(dViewUpX, dViewUpY, dViewUpZ);
pCamera->SetParallelProjection(bParallelProjection ? 1 : 0);
pCamera->OrthogonalizeViewUp();
m_pRenderer->ResetCamera(dBounds);
m_pRenderer->ResetCameraClippingRange(dBounds);
return true;
}
bool nmVTKCameraController::zoom(double dFactor)
{
Q_ASSERT(isCameraGuiThread());
double dBounds[6];
if(!isCameraGuiThread() || !vtkMath::IsFinite(dFactor) ||
dFactor <= 0.0 || !getVisibleBounds(dBounds)) {
return false;
}
vtkCamera* pCamera = m_pRenderer->GetActiveCamera();
if(pCamera == nullptr) {
return false;
}
// 正交投影必须调整 ParallelScaleDolly 只对透视投影产生可见缩放。
if(pCamera->GetParallelProjection() != 0) {
const double dParallelScale = pCamera->GetParallelScale();
if(!vtkMath::IsFinite(dParallelScale) || dParallelScale <= 0.0) {
return false;
}
pCamera->SetParallelScale(dParallelScale / dFactor);
} else {
pCamera->Dolly(dFactor);
}
m_pRenderer->ResetCameraClippingRange(dBounds);
return true;
}