Axiom3D:Ogre射线与点,线,面相交,鼠标操作3维空间.
在第一篇网络分解成点,线,面.第二篇分别点以球形,线以圆柱,面分别以MergerBatch整合批次显示.因为整合批次显示后,相应的点,线,面不能以Ogre本身的射线来选取,因为整合后,以点举例,多个点显示虽然不在一起,但是是一个Mesh.Ogre本身的检测只能检测到这里,在我们这不满足要求,相应的点,线,面检测都需要自己来计算.
在讲解本文之前,先看下射线的相关生成代码,只有先明白射线如何生成,生成最后是相对什么空间.
[OgreVersion( 1, 7, 2790, "Slightly different" )] public void GetCameraToViewportRay( float screenX, float screenY, out Ray ray ) { var inverseVP = ( _projectionMatrix*_viewMatrix ).Inverse(); #if !AXIOM_NO_VIEWPORT_ORIENTATIONMODE // We need to convert screen point to our oriented viewport (temp solution) Real tX = screenX; Real a = (int)OrientationMode*System.Math.PI*0.5f; screenX = System.Math.Cos( a )*( tX - 0.5f ) + System.Math.Sin( a )*( screenY - 0.5f ) + 0.5f; screenY = System.Math.Sin( a )*( tX - 0.5f ) + System.Math.Cos( a )*( screenY - 0.5f ) + 0.5f; if ( ( ( (int)OrientationMode ) & 1 ) == 1 ) { screenY = 1.0f - screenY; } #endif Real nx = ( 2.0f*screenX ) - 1.0f; Real ny = 1.0f - ( 2.0f*screenY ); var nearPoint = new Vector3( nx, ny, -1.0f ); // Use midPoint rather than far point to avoid issues with infinite projection var midPoint = new Vector3( nx, ny, 0.0f ); // Get ray origin and ray target on near plane in world space var rayOrigin = inverseVP*nearPoint; var rayTarget = inverseVP*midPoint; var rayDirection = rayTarget - rayOrigin; rayDirection.Normalize(); ray = new Ray( rayOrigin, rayDirection ); }
这个方法在摄像机类,主要因为摄像机内整合了视图矩阵与透视矩阵,再说下视图矩阵与透视矩阵的作用,视图矩阵相当于把世界坐标变换成摄像机坐标系,我们平常用Camear.Lookat来完成,一个参数是当前位置,一个是向上向量,一个是指向目标向量.这三个参数分别用来指定视图坐标系的圆点,指向目标向量是Z轴,向上向量是Y轴,根据YZ向量的叉积生成X轴,然后X轴与Z轴的叉积会重新生成Y轴,为什么Y轴后面会重新生成,因为我们目标向量可能一直在变,本来不可能一直垂直Y轴,而在我们视图坐标系中,三轴垂直,Y轴更多用来辅助生成X轴.比如我们不设置Camear.Lookat,那么默认视图坐标系与世界坐标对应关系如下,目标在原点,而Z轴是Vector3.zero-Vector3.Z,就是负Z轴,Y轴还是Vector3.Y,根据z与y生成x轴对应的是-Vector3.X,然后生成Y轴,和原来一样,总的来说,就是一个和世界坐标系同原点,但是x,z是负方向,y的原来一样的坐标系.
而透视矩阵是在视图矩阵后,把视图里视椎体(一般四个参数,近截而距离,远截面距离,上下视角,左右比例)映射成一个长宽高各为(-1,1)的立方体,在Ogre与opengl都是如此,在dx中是(0,1)的立方体,这个关系不大.这里不说具体如何生成矩阵,只讲这个矩阵的含义.在经过透视矩阵后生成的x,y,z三个方向各为(-1,1)的立方体中,x方向的(-1,1)对应的屏幕的宽度,而y方向对应是屏幕的高度,z对应的是深度,主要用来判断那个物体在前,那个物体在后,根据深度可以添加很多特殊效果.
物体一般的变换大致如下,如果物体本身有个局部坐标系,需要先从局部坐标系换成世界坐标系,再从世界坐标系换成屏幕坐标系,最后经过透视矩阵转换成屏幕上的点.
那么我们用鼠标点击一下,就是屏幕上的点,如何换算成3D里面的坐标系了,Ogre里的GetCameraToViewportRay就给我们做了一个很好的演视,或者说是把上面的过程反推回去,首先根据点在视图中的位置x/width,y/hight生成0-1的浮点值,在前面我们知道,透视矩阵后的长宽都是-1到1的,所以先要把这里的0,1的值映射成-1,1,很简单2*x-1.需要注意的是,在屏幕里,Y是从上向下,而我们世界坐标系Y是从下向上,所以首先把y倒过来取负.x与y确定后,z值不确定.这很正常,二维变成三维,本来就少一维的信息,在这里我们生成射线就好说了,分别是近点z=-1,和远点z=1生成射线就行,这边没取z=1,而是z=0,代码上有解释,中间的点比最远的的那点好,避免无限远投影的问题.那么在这我们就生成了原经过透视矩阵后的坐标,如果把这坐标换成世界坐标了,很简单来,原来是世界坐标经过视图与透视矩阵后成透视坐标,我们只要把对应透视坐标剩以视图与透视矩阵的逆矩阵就成了世界坐标系中的位置.我们知道矩阵剩以他自己的逆矩阵等于1.根据这两点最后生成射线表示法的一种.P(t) = P0 + t*D.P0是原点,D是方向,t是沿方向D前进的长度.
经过上面我们知道,通过GetCameraToViewportRay生成的射线是世界坐标系下的,相应的运算在同一坐标系下算才有意义.下面是点选择的效果.
如图上,八个点是一个Mesh,可以分别设置大小,颜色等,具体设计请看上一篇.点的选择比较简单,把点当然一个球形,用Ogre自带的射线与球相交检查就可.
public void HitTest(Ray ray) { foreach (var p in this) { Sphere sphere = new Sphere(this.Parent.PointNode.FullTransform * p.VertexInfo.Position, this.Scale); var result = Utility.Intersects(ray, sphere); if (result.Hit) { this.SelectPoint = p; break; } } }