跳转至

屏幕坐标转世界坐标

  1. Qt 的屏幕坐标系(paintEvent 绘制使用) 二维坐标系,原点在左上角。

  2. VTK 的 Display 坐标系(VTK 屏幕绘制坐标系) 二维坐标系,原点在左下角,右手坐标系。

  3. VTK 的 Normalized Display 坐标系 Display 坐标的归一化形式,常用于:多个 renderer 时的屏幕分割、屏幕指示控件(如 vtkOrientationMarkerWidget)的位置定义。

  4. VTK 相机的 View 坐标系 相对于相机的位置坐标系,x / y / z 通常在 [-1, 1] 范围内。

  5. VTK 的 World 坐标系 三维实际空间坐标,用于描述模型的真实位置。

  6. VTK 的 Viewport 与 Normalized Viewport 实际上对应 vtkRenderer 的渲染区域。

从抽象层面可以概括为三类坐标系:

  • 世界坐标

  • 视图坐标(计算机图形学中的渲染坐标系)

  • 显示坐标(显示设备上的实际屏幕坐标)

1 角度测量

角度测量可以直接基于 Qt 屏幕坐标系 进行计算,不需要转换到 VTK 坐标系。 只需使用屏幕上的点,计算两条直线之间的夹角即可。

double GeneralAlgorithm::IncludedAngleLine(
  QPointF pos1, QPointF pos2, QPointF pos3, QPointF pos4)
{
    double k1 = (pos2.y() - pos1.y()) / (pos2.x() - pos1.x());
    double k2 = (pos4.y() - pos3.y()) / (pos4.x() - pos3.x());
    return atan(abs((k2 - k1) / (1 + k1 * k2))) * 57.29578;
}

2 距离测量

距离测量需要将 QOpenGLWidget 的屏幕坐标 转换到 VTK 世界坐标系。

由于 Qt 的屏幕坐标原点在左上,而 VTK 的屏幕坐标原点在左下,因此需要对 Y 轴进行反转,例如:

1023 - pos1.y(); // 其中 1024 为 VTK 渲染窗口的高度。

VTK 屏幕坐标转换为世界坐标时:

  • 若点位于 焦平面上,可以直接使用 Viewport 的 WorldToDisplay / DisplayToWorld。

  • 若存在深度偏移,则需要区分 平行投影 与 透视投影。

可以看到,VTK 中很多自带的 rep 和 handle 都分为 2D 和 3D 两种实现,其核心区别正是是否需要考虑投影方式:

  • 2D(平行投影):直接使用现成的 vtkCoordinate。

  • 3D(平行 / 透视):根据 ActiveCamera 手动计算(如下代码中注释部分所示)。

double GeneralAlgorithm::ComputeWorldPosition(
  vtkRenderer *ren, double displayPos[], double worldPos[])
{
    double fp[4];
    ren->GetActiveCamera()->GetFocalPoint(fp);
    fp[3] = 1.0;

    ren->SetWorldPoint(fp);
    ren->WorldToDisplay();
    ren->GetDisplayPoint(fp);

    double tmp[4];
    tmp[0] = displayPos[0];
    tmp[1] = displayPos[1];
    tmp[2] = fp[2];
    ren->SetDisplayPoint(tmp);
    ren->DisplayToWorld();
    ren->GetWorldPoint(tmp);

    // 沿观察方向从焦平面“偏移”平移焦点。(我这里测量距离不需要)
    //    double Offset = 0.0;
    //    double focalPlaneNormal[3];
    //    ren->GetActiveCamera()->GetDirectionOfProjection(focalPlaneNormal);
    //    if (ren->GetActiveCamera()->GetParallelProjection()) {
    //        tmp[0] += (focalPlaneNormal[0] * Offset);
    //        tmp[1] += (focalPlaneNormal[1] * Offset);
    //        tmp[2] += (focalPlaneNormal[2] * Offset);
    //    } else {
    //        double camPos[3], viewDirection[3];
    //        ren->GetActiveCamera()->GetPosition(camPos);
    //        viewDirection[0] = tmp[0] - camPos[0];
    //        viewDirection[1] = tmp[1] - camPos[1];
    //        viewDirection[2] = tmp[2] - camPos[2];
    //        vtkMath::Normalize(viewDirection);
    //        double costheta = vtkMath::Dot(viewDirection, focalPlaneNormal)
    //          / (vtkMath::Norm(viewDirection) * vtkMath::Norm(focalPlaneNormal));
    //        if (costheta != 0.0) // 透视投影中0.0不可能
    //        {
    //            tmp[0] += (viewDirection[0] * Offset / costheta);
    //            tmp[1] += (viewDirection[1] * Offset / costheta);
    //            tmp[2] += (viewDirection[2] * Offset / costheta);
    //        }
    //    }

    worldPos[0] = tmp[0];
    worldPos[1] = tmp[1];
    worldPos[2] = tmp[2];
    return 1;
}

double GeneralAlgorithm::GetDistance(
  QPointF pos1, QPointF pos2, vtkRenderer *renderer)
{
    double p1[3], p2[3];
    double display1[2] { pos1.x(), 1023 - pos1.y() },
      display2[2] { pos2.x(), 1023 - pos2.y() };
    ComputeWorldPosition(renderer, display1, p1);
    ComputeWorldPosition(renderer, display2, p2);
    double distance = sqrt(vtkMath::Distance2BetweenPoints(p1, p2));
    return distance;
}