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/nmVTKPlanarPolygonInteracto...

302 lines
8.5 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 "nmVTKPlanarPolygonInteractorStyle.h"
#include <QtGlobal>
#include <vtkMath.h>
#include <vtkObjectFactory.h>
#include <vtkRenderer.h>
#include <vtkRenderWindowInteractor.h>
#include <cmath>
#include <cstring>
#include <limits>
vtkStandardNewMacro(nmVTKPlanarPolygonInteractorStyle);
namespace {
const double PLANAR_POLYGON_RAY_EPSILON = 1.0e-12;
bool isFinitePoint(const QPointF& oPoint)
{
return vtkMath::IsFinite(oPoint.x()) &&
vtkMath::IsFinite(oPoint.y());
}
bool displayToWorld(vtkRenderer* pRenderer,
double dX,
double dY,
double dZ,
double dWorld[3])
{
if(pRenderer == nullptr || dWorld == nullptr ||
!vtkMath::IsFinite(dX) || !vtkMath::IsFinite(dY) ||
!vtkMath::IsFinite(dZ)) {
return false;
}
// VTK 返回齐次世界坐标,必须先除以 w 才能参与射线计算。
pRenderer->SetDisplayPoint(dX, dY, dZ);
pRenderer->DisplayToWorld();
const double* pHomogeneous = pRenderer->GetWorldPoint();
if(pHomogeneous == nullptr ||
!vtkMath::IsFinite(pHomogeneous[0]) ||
!vtkMath::IsFinite(pHomogeneous[1]) ||
!vtkMath::IsFinite(pHomogeneous[2]) ||
!vtkMath::IsFinite(pHomogeneous[3]) ||
std::fabs(pHomogeneous[3]) <=
std::numeric_limits<double>::epsilon()) {
return false;
}
const double dInverseW = 1.0 / pHomogeneous[3];
dWorld[0] = pHomogeneous[0] * dInverseW;
dWorld[1] = pHomogeneous[1] * dInverseW;
dWorld[2] = pHomogeneous[2] * dInverseW;
return vtkMath::IsFinite(dWorld[0]) &&
vtkMath::IsFinite(dWorld[1]) &&
vtkMath::IsFinite(dWorld[2]);
}
double coordinateScale(const QPointF& oLeft, const QPointF& oRight)
{
double dScale = 1.0;
dScale = qMax(dScale, std::fabs(oLeft.x()));
dScale = qMax(dScale, std::fabs(oLeft.y()));
dScale = qMax(dScale, std::fabs(oRight.x()));
dScale = qMax(dScale, std::fabs(oRight.y()));
return dScale;
}
bool isSamePoint(const QPointF& oLeft, const QPointF& oRight)
{
const double dScale = coordinateScale(oLeft, oRight);
const double dTolerance = 1.0e-10 * dScale;
const double dX = oLeft.x() - oRight.x();
const double dY = oLeft.y() - oRight.y();
return dX * dX + dY * dY <= dTolerance * dTolerance;
}
bool hasNonZeroArea(const QVector<QPointF>& vecPoints)
{
if(vecPoints.size() < 3) {
return false;
}
// 以首点为局部原点计算面积,避免大坐标下的精度损失。
const QPointF oOrigin = vecPoints.first();
double dMinimumX = oOrigin.x();
double dMaximumX = oOrigin.x();
double dMinimumY = oOrigin.y();
double dMaximumY = oOrigin.y();
double dTwiceArea = 0.0;
for(int nIndex = 0; nIndex < vecPoints.size(); ++nIndex) {
const QPointF& oCurrent = vecPoints[nIndex];
const QPointF& oNext =
vecPoints[(nIndex + 1) % vecPoints.size()];
const double dCurrentX = oCurrent.x() - oOrigin.x();
const double dCurrentY = oCurrent.y() - oOrigin.y();
const double dNextX = oNext.x() - oOrigin.x();
const double dNextY = oNext.y() - oOrigin.y();
dTwiceArea += dCurrentX * dNextY - dNextX * dCurrentY;
dMinimumX = qMin(dMinimumX, oCurrent.x());
dMaximumX = qMax(dMaximumX, oCurrent.x());
dMinimumY = qMin(dMinimumY, oCurrent.y());
dMaximumY = qMax(dMaximumY, oCurrent.y());
}
const double dScale = qMax(1.0,
qMax(dMaximumX - dMinimumX, dMaximumY - dMinimumY));
return vtkMath::IsFinite(dTwiceArea) &&
std::fabs(dTwiceArea) > 1.0e-12 * dScale * dScale;
}
}
nmVTKPlanarPolygonInteractorStyle::
nmVTKPlanarPolygonInteractorStyle()
: m_bCompleted(false)
{
}
nmVTKPlanarPolygonInteractorStyle::
~nmVTKPlanarPolygonInteractorStyle()
{
}
void nmVTKPlanarPolygonInteractorStyle::OnLeftButtonDown()
{
if(m_bCompleted || this->Interactor == nullptr) {
return;
}
// 单击追加顶点,VTK 将双击的第二次按下标记为 RepeatCount > 0。
if(this->Interactor->GetRepeatCount() > 0) {
completePolygon();
return;
}
appendCurrentPoint();
}
void nmVTKPlanarPolygonInteractorStyle::OnRightButtonDown()
{
// 绘制模式下右键专用于取消,不传递给默认相机或右键菜单。
if(!m_bCompleted) {
cancelPolygon();
}
}
void nmVTKPlanarPolygonInteractorStyle::OnKeyPress()
{
if(m_bCompleted || this->Interactor == nullptr) {
return;
}
const char* pKeySym = this->Interactor->GetKeySym();
if(pKeySym == nullptr) {
return;
}
// Enter 完成、Backspace/Delete 撤回末点、Escape 取消,均由业务状态机收口。
if(std::strcmp(pKeySym, "Return") == 0 ||
std::strcmp(pKeySym, "KP_Enter") == 0) {
completePolygon();
} else if(std::strcmp(pKeySym, "BackSpace") == 0 ||
std::strcmp(pKeySym, "Delete") == 0) {
removeLastPoint();
} else if(std::strcmp(pKeySym, "Escape") == 0) {
cancelPolygon();
}
}
void nmVTKPlanarPolygonInteractorStyle::OnChar()
{
// 字符命令在绘制模式没有业务含义,不能触发基类的退出或相机复位。
}
void nmVTKPlanarPolygonInteractorStyle::reset()
{
m_vecPoints.clear();
m_bCompleted = false;
}
QVector<QPointF>
nmVTKPlanarPolygonInteractorStyle::getPoints() const
{
return m_vecPoints;
}
int nmVTKPlanarPolygonInteractorStyle::getPointCount() const
{
return m_vecPoints.size();
}
bool nmVTKPlanarPolygonInteractorStyle::isCompleted() const
{
return m_bCompleted;
}
bool nmVTKPlanarPolygonInteractorStyle::projectEventPositionToPlane(
QPointF& oPoint)
{
if(this->Interactor == nullptr) {
return false;
}
int* pEventPosition = this->Interactor->GetEventPosition();
if(pEventPosition == nullptr) {
return false;
}
this->FindPokedRenderer(pEventPosition[0], pEventPosition[1]);
vtkRenderer* pRenderer = this->CurrentRenderer;
if(pRenderer == nullptr) {
return false;
}
// 将同一屏幕点的近、远裁剪面坐标连成射线,再与 z=0 网格平面求交。
double dNearPoint[3];
double dFarPoint[3];
if(!displayToWorld(pRenderer,
pEventPosition[0], pEventPosition[1], 0.0,
dNearPoint) ||
!displayToWorld(pRenderer,
pEventPosition[0], pEventPosition[1], 1.0,
dFarPoint)) {
return false;
}
const double dDirectionZ = dFarPoint[2] - dNearPoint[2];
const double dRayScale = qMax(1.0,
qMax(std::fabs(dNearPoint[2]), std::fabs(dFarPoint[2])));
if(std::fabs(dDirectionZ) <=
PLANAR_POLYGON_RAY_EPSILON * dRayScale) {
return false;
}
const double dParameter = -dNearPoint[2] / dDirectionZ;
const QPointF oCandidate(
dNearPoint[0] +
dParameter * (dFarPoint[0] - dNearPoint[0]),
dNearPoint[1] +
dParameter * (dFarPoint[1] - dNearPoint[1]));
if(!isFinitePoint(oCandidate)) {
return false;
}
oPoint = oCandidate;
return true;
}
bool nmVTKPlanarPolygonInteractorStyle::appendCurrentPoint()
{
QPointF oPoint;
if(!projectEventPositionToPlane(oPoint) ||
(!m_vecPoints.isEmpty() &&
isSamePoint(m_vecPoints.last(), oPoint))) {
return false;
}
// 连续重合点不入队,否则会生成零长边并干扰后续三角化。
m_vecPoints.append(oPoint);
this->InvokeEvent(PolygonChangedEvent);
renderInteractor();
return true;
}
bool nmVTKPlanarPolygonInteractorStyle::completePolygon()
{
if(m_bCompleted) {
return true;
}
if(!hasNonZeroArea(m_vecPoints)) {
this->InvokeEvent(PolygonInvalidEvent);
return false;
}
// 先发变更事件绘制闭合边,再通知业务层离开 VTK 交互栈。
m_bCompleted = true;
this->InvokeEvent(PolygonChangedEvent);
renderInteractor();
this->InvokeEvent(PolygonCompletedEvent);
return true;
}
void nmVTKPlanarPolygonInteractorStyle::removeLastPoint()
{
if(m_vecPoints.isEmpty()) {
return;
}
m_vecPoints.remove(m_vecPoints.size() - 1);
this->InvokeEvent(PolygonChangedEvent);
renderInteractor();
}
void nmVTKPlanarPolygonInteractorStyle::cancelPolygon()
{
this->InvokeEvent(PolygonCancelledEvent);
}
void nmVTKPlanarPolygonInteractorStyle::renderInteractor()
{
if(this->Interactor != nullptr) {
this->Interactor->Render();
}
}