屏幕坐标转世界坐标¶
-
Qt 的屏幕坐标系(paintEvent 绘制使用) 二维坐标系,原点在左上角。
-
VTK 的 Display 坐标系(VTK 屏幕绘制坐标系) 二维坐标系,原点在左下角,右手坐标系。
-
VTK 的 Normalized Display 坐标系 Display 坐标的归一化形式,常用于:多个 renderer 时的屏幕分割、屏幕指示控件(如 vtkOrientationMarkerWidget)的位置定义。
-
VTK 相机的 View 坐标系 相对于相机的位置坐标系,x / y / z 通常在 [-1, 1] 范围内。
-
VTK 的 World 坐标系 三维实际空间坐标,用于描述模型的真实位置。
-
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 轴进行反转,例如:
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;
}