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