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

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