写在前面
C# Tips是博主开启的第一个项目,以C#为示例语言,旨在普及编程技巧、经典算法以及计算机视觉、图形学知识。
抓取点
第一节中提到过Model3DNode类继承了PickableNode类,PickableNode类提供了用来抓取模型中三维点的方法,抓取点有以下一些目的:
- 显示单个点云的属性,包括坐标、颜色和其他自定义属性;
- 为长度、面积、体积量测提供位置信息;
- 切换视图的旋转中心。
抓取点分为以下几个步骤:
- 实时高亮显示可抓取的三维点;
- 添加WinGLCanvas控件的鼠标点击事件,比如双击;
- 屏幕坐标系向模型坐标系的转换;
- 消除已抓取点的抓取记录。
高亮显示
如需实时抓取三维点,首先需要创建一个适应于整个场景的Picking 类实例:
Picking pickingAction = new Picking(scene);
如需高亮显示,还要创建一个LegacyPointNode类实例来存储抓取结果:
LegacyPointNode highlightPt = new LegacyPointNode();
为达到实时高亮显示的目的,必须为WinGLCanvas控件添加一个鼠标移动事件winGLCanvas1_MouseMove,Picking类提供的Pick方法很容易获得抓取处的图形坐标,抓取坐标存储于PickedGeometry类中(其z坐标只表示屏幕深度),如果PickedGeometry为null,则说明未成功抓取三维点。
private void winGLCanvas1_MouseMove(object sender, MouseEventArgs e)
{
if (this.pickingAction != null)
{
IGLCanvas canvas = this.winGLCanvas1;
int x = e.X;
int y = canvas.Height - e.Y - 1;
this.pickedGeometry = this.pickingAction.Pick(x, y, PickingGeometryTypes.Point, canvas.Width, canvas.Height);
if (this.pickedGeometry != null)
{
highlightPt.Color = new vec3(1, 0, 0);
highlightPt.Vertex = this.pickedGeometry.Positions[0];
highlightPt.Parent = this.pickedGeometry.FromObject as SceneNodeBase;
}
}
else
{
this.pickedGeometry = null;
}
}
坐标转换
当鼠标点下后,我们通常需要根据屏幕坐标(lastWindowSpacePos)获取抓取点的模型坐标(lastModelSpacePos ),这个过程实际上是三维模型可视化的逆过程,也就是通过已知的MVP矩阵反向投影得到模型坐标,glm提供的unProject很容易解决这个问题。
private void winGLCanvas1_MouseDoubleClick(object sender, MouseEventArgs e)
{
if (this.pickingAction != null)
{
if (e.Button == MouseButtons.Left)
{
if (this.pickedGeometry != null)
{
IGLCanvas canvas = this.winGLCanvas1;
int x = e.X;
int y = canvas.Height - e.Y - 1;
var viewport = new vec4(0, 0, canvas.Width, canvas.Height);
var lastWindowSpacePos = new vec3(x, y, pickedGeometry.PickedPosition.z);
mat4 projectionMat = this.scene.Camera.GetProjectionMatrix();
mat4 viewMat = this.scene.Camera.GetViewMatrix();
mat4 modelMat = (pickedGeometry.FromObject as PickableNode).GetModelMatrix();
var lastModelSpacePos = glm.unProject(lastWindowSpacePos, viewMat * modelMat, projectionMat, viewport);
}
}
}
}
消除记录
如果需要抓取新的三维点,那么之前的抓取记录就应该被消除:
scene.RootNode.Children.Clear();