在第一篇网络分解成点,线,面.第二篇分别点以球形,线以圆柱,面分别以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;}
#endifReal 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 projectionvar midPoint = new Vector3( nx, ny, 0.0f );// Get ray origin and ray target on near plane in world spacevar rayOrigin = inverseVP*nearPoint;var rayTarget = inverseVP*midPoint;var rayDirection = rayTarget - rayOrigin;rayDirection.Normalize();ray = new Ray( rayOrigin, rayDirection );}

Ogre 射线生成

  这个方法在摄像机类,主要因为摄像机内整合了视图矩阵与透视矩阵,再说下视图矩阵与透视矩阵的作用,视图矩阵相当于把世界坐标变换成摄像机坐标系,我们平常用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;}}}

Point Hit

  这个算是比较简单的,只需要注意一点的,原来点是局部坐标系下的,需要剩以本节点下的局部坐标下,转换成世界坐标再与我们的Ray比较.

  下面是线的选择,同样虽然是多条线,可以不同的颜色,但是只有一个Batch.先看下效果图.

  具体算法见如下代码:

        /// <summary>/// 3D数学基础:图形与游戏开发 里的算法,二射线相交检测/// </summary>/// <param name="ray1"></param>/// <param name="ray2"></param>/// <param name="deviation">ray1与ray2最近距离误差范围</param>/// <returns></returns>public static Tuple<bool, Real, Real, Vector3, Vector3> RayIntersectsRay(this Ray ray1, Ray ray2, float deviation){var OfO = ray2.Origin - ray1.Origin;var DxD = ray1.Direction.Cross(ray2.Direction);//平行if (DxD == Vector3.Zero){//1重合,2不重合,重合也相当于hit.float limit = 0.00001f;var t = ray2.Origin.x - ray1.Origin.x;var v = ray1.Origin + ray1.Direction * t;//查看ray2.Origin是否在ray1.Origin射线中if (v.DifferenceLessThan(ray2.Origin, limit)){return Tuple.Create(true, (Real)t, (Real)0, v, ray2.Origin);}return Tuple.Create(false, (Real)0, (Real)0, Vector3.Invalid, Vector3.Invalid);}else{var t1 = OfO.Cross(ray2.Direction).Dot(DxD) / DxD.Dot(DxD);var t2 = OfO.Cross(ray1.Direction).Dot(DxD) / DxD.Dot(DxD);var p1 = ray1.Origin + ray1.Direction * t1;var p2 = ray2.Origin + ray2.Direction * t2;bool bHit = (p1 - p2).Length < deviation;return Tuple.Create(bHit, t1, t2, p1, p2);}}

line hit

  这个算法是参照3D数学基础:图形与游戏开发里的算法,二射线相交检测.前面平行(分二种,一种重合,一种不重合)的情况没碰到,也就没测,我们主要看不平行的情况,也有二种,一种是在同一平面,一种是不在同一平面.在同一平面的情况不多,更多的是不在同一平面的情况,这种的话并不是就说相交不到,因为我们本来选择的是线段,在这以圆柱画出的,只要检测二射线相离最近的二点在一个范围内就算相交,这个具体的推导算法就不解释了,大家有兴趣可以去看下3D数学基础:图形与游戏开发,里面有完整的推导过程.不过上面明显还不完善,还要加上如下代码.  

       public void HitTest(Ray ray){foreach (var line in this){var p1 = this.Parent.LineNode.FullTransform * this.Parent[line.Index[0]];var p2 = this.Parent.LineNode.FullTransform * this.Parent[line.Index[1]];//3D数学基础:图形与游戏开发 里的算法,二射线相交检测//s1 ray s2 (p1 + (p2-p1)*t)Ray ray0 = new Ray(p1, (p2 - p1).ToNormalized());var hitResult = ray0.RayIntersectsRay(ray, this.Scale);if (hitResult.Item1){var t = hitResult.Item2;if (t >= 0 && t <= (p2 - p1).Length){this.SelectLine = line;break;}}}}

line hit seg

  因为我们选择的是线段,所以不仅要检测相交,还要检测交点是否在有效范围内.最后同上面,对应的点应该转化到世界坐标系下.

  最后是面,面就不发图了,直接放代码,流程和上面差不多.

       public void HitTest(Ray ray){foreach (var surface in this){foreach (var tri in surface.Triangles){var matrix = this.Parent.LineNode.FullTransform;var p1 = matrix * this.Parent[tri.sharedVertIndex[0]];var p2 = matrix * this.Parent[tri.sharedVertIndex[1]];var p3 = matrix * this.Parent[tri.sharedVertIndex[2]];var result = ray.RayIntersectsTriangle(p1, p2, p3);if (result.Item1){this.SelectSurface = surface;break;}}}}public static System.Tuple<bool, Vector3> RayIntersectsTriangle(this Ray ray, Vector3 a, Vector3 b, Vector3 c){// Place the end beyond any conceivable triangle, 1000 meters awayvar start = ray.Origin;var end = start + ray.Direction * 1000000f;var pq = end - start;var pa = a - start;var pb = b - start;var pc = c - start;// Test if pq is inside the edges bc, ca and ab. Done by testing// that the signed tetrahedral volumes, computed using scalar triple// products, are all positivevar result = Tuple.Create(false, Vector3.Invalid);float u = pq.Cross(pc).Dot(pb);if (u < 0.0f){return result;}float v = pq.Cross(pa).Dot(pc);if (v < 0.0f){return result;}float w = pq.Cross(pb).Dot(pa);if (w < 0.0f){return result;}var denom = 1.0f / (u + v + w);// Finally fill in the intersection pointvar where = (u * a + v * b + w * c) * denom;return Tuple.Create(true, where);}

surface hit

  RayIntersectsTriangle算法是Axiom3D论坛里有人提供的,大家可以搜索下.

  经过上面点,线,面选择后,我们来完善前面没有完善的地方,在上面截图控件上方,手掌一样的东东用来移动模型,最开始直接用鼠标移动来对应物体的x,y移动,当然效果烂的要命,网上搜了一下,都没昨讲.没有办法,只有自己动手了,前面我们看到Ray的生成过程,鼠标点击后,这点映射成三维里的,x,y好确定,唯一没想通如何确定z值.后面在脑海里把视截体想了下,如果能确定我们要移动的目标在视截体距离近截面和远截面的位置(后面发现只要用到与近截面的距离,具体后面再来说),不就可以根据这个比例映射在ray上的长度,就是前面P(t) = P0 + t*D.这里面的t值.

  下面是相关代码,鼠标左键点下后,我们确定与近截面的距离,viewD.

if (EngineCore.Instance.ActionType != ActionType.None)
{EngineCore.Instance.Select = true;//hitD = this.Camera;this.viewZ = this.Camera.FrustumPlanes[0].GetDistance(node.DerivedPosition);}

移动的目标与近截面的距离

  鼠标移动的代码:

        public override void MouseMove(MouseEventArgs e){int offsetX = e.X - prePoint.X;int offsetY = e.Y - prePoint.Y;if (offsetX == 0 && offsetY == 0)return;if (Control.MouseButtons == MouseButtons.Left){if (EngineCore.Instance.Select){var node = EngineCore.Instance.ViewNode;if (EngineCore.Instance.ActionType == ActionType.Translate){var ray = this.CreateViewportRay(e.X, e.Y);var pos = ray.Origin + ray.Direction * this.viewZ;node.DerivedPosition = pos;}else if (EngineCore.Instance.ActionType == ActionType.Rotate){node.Yaw((Real)(new Degree((Real)(offsetX * 0.15f))));node.Pitch((Real)(new Degree((Real)(offsetY * 0.15f))));}else if (EngineCore.Instance.ActionType == ActionType.Scale){}}else{Real dist = (this.camera.Position - EngineCore.Instance.ViewNode.DerivedPosition).Length;this.camera.Position = EngineCore.Instance.ViewNode.DerivedPosition;this.camera.Yaw((Real)(new Degree((Real)(-offsetX * 0.25f))));this.camera.Pitch((Real)(new Degree((Real)(-offsetY * 0.25f))));this.camera.MoveRelative(new Vector3(0, 0, dist));EngineCore.Instance.AxisNode.Yaw((Real)(new Degree((Real)(-offsetX * 0.25f))));EngineCore.Instance.AxisNode.Pitch((Real)(new Degree((Real)(-offsetY * 0.25f))));}}this.renderWindow.Update();prePoint = e.Location;base.MouseMove(e);}

鼠标移动事件

  结合前面讲解Ray的生成过程,我们知道,ray.Origin是鼠标点击的近截面上的点,而Direction是方向,因为已经归一化,也就是说这个是单位向量,那么我们只需要知道移动的目标与近截面的距离,就能得到我们点击z值了.

  效果就是鼠标左键按下是确定与近截面的距离,然后移动的时候,在视截体里,x,y是变化的,z固定的.测试了一下,效果不错,很满意,如我视点移到Y轴上,与x,z面垂直,就能保证移动物体在x,z面,根据不同的视角移动,现实感还是很强,也没有感觉不合理的位置.

  旋转就比较简单了,直接把鼠标的二维坐标映射过去就成,效果也还行.代码也在上面,不单独说了,缩放感觉放鼠标滚轮处理比较好,暂时不管了,就个很容易.最后是没选择物体是,摄像机会围绕模型旋转,具体实现大家看下代码就明白了.

  新的一年第一天,确定今年主要自学内容,C++ 11与Ogre,在这个项目完成后,主要重新啃C++,最好能用Ogre实现一些功能.

  

转载于:https://www.cnblogs.com/zhouxin/p/4197975.html

Axiom3D:Ogre射线与点,线,面相交,鼠标操作3维空间.相关推荐

  1. 射线与圆、球相交检测

    射线与圆.球相交检测 本篇讨论2D中射线和圆的相交检测,本方法同样适用于3D中射线和球的相交检测,这是因为可以在包含射线和球心的平面中进行检测,从而将3D问题转化为2D问题.如果射线穿过球心,那么平面 ...

  2. Revit2014: 板Slab在创建时候提示错误:“边界边缘线彼此相交。边界等高线不可扭曲。”

    在Revit 2014里面,调用NewSlab方法抛出一个奇怪的错误: Boundary edge lines intersect each other.  Boundary contour must ...

  3. C什么k什么_K线图基础知识丨什么是K线散兵坑形态?K线散兵坑形态的操作与案例详解...

    本文总共999字,预计阅读需要3分钟 今天我们给大家带来的是K线散兵坑形态,那么K线散兵坑形态应该怎么来操作呢?今天我们就结合相应的实战案例来为大家讲解一下吧. K线散兵坑形态的简介 散兵坑是指在股价 ...

  4. linux otg 鼠标 节点,otg 的host功能,使用otg转host的线,连接鼠标。无效。

    otg 的host功能,使用otg转host的线,连接鼠标.无效. 看了一下log, 有一个切换动作,但又马上切换成device. force_usb_mode_store 2->1 [   1 ...

  5. HQChart使用教程5- K线图控件操作函数说明

    K线图控件操作函数说明 周期切换 切换股票 切换指标 增加一个窗口指标 AddIndexWindow indexName option 增加一个自定义通达信脚本指标窗口 AddScriptIndexW ...

  6. CAD-VB多段线、波浪线、射线、构造线

    '多段线Private Sub CommandButton1_Click() Dim duoduanxian As AcadPolyline Dim erweishuzu(14) As Double ...

  7. unity 射线 碰撞 连线烘焙

    点击选中物体 方法1:OnMouseDown()只针对脚本挂载的物体有效 首先,要给 物体加上 碰撞器Collider 然后在物体脚本上加入 void OnMouseDown()//只针对脚本挂载的物 ...

  8. 工程师追查线上问题(或运维)常用的shell命令

    shell本身是非常强大的,而工程师在追查线上问题时,如何能够更加快速更加有效的定位问题,用好shell非常关键.下面是我总结的几个在追查问题时常用的shell命令.大家可以参考下.大家有好的命令,也 ...

  9. canvas实现动态点线背景,鼠标画点连线。

    html: <canvas id="canvas"></canvas> css: canvas{display: block;width: 100%;hei ...

最新文章

  1. C/C++ 编程规范(02)— 标识符命名
  2. Spark 分布式计算原理
  3. 用ProFTPD构建FTP服务器
  4. 前端学习(1426):ajax封装
  5. java学习(61):适配器
  6. C++ STL deque创建
  7. 测试用例编写注意事项
  8. Bailian3247 回文素数【素数+回文】(POJ NOI0113-11)
  9. [转载] Python字典的setdefault()方法
  10. struts2教程_Struts 2教程– Struts2教程
  11. 创建和删除目录mkdir/rmdi
  12. HTML-参考手册: HTML 符号实体
  13. JVM监控及诊断工具GUI篇之Arthas(一):基础指令
  14. python基础:sys模块
  15. anjuta 连接mysql_buntu下的可视化C/C++编译器anjuta配置的方法
  16. Windows安装pytorch,傻瓜式教程
  17. 感悟-关于爱情(一年半,最后一次告别)
  18. 编写 SQL 查询表格,按创建时间降序排列。
  19. 倭黑猩猩机器人_难以置信!倭黑猩猩竟也有助产“护士”
  20. Python语言在地球科学领域中的应用

热门文章

  1. MySql 应该选择普通索引 还是唯一 索引???
  2. Android 屏幕适配攻略(六)设置通知样图标与启动图标适配
  3. china-pub近7日计算机图书排行榜
  4. nginx 1.4.3能直接升到1.8.1吗
  5. vs2015企业版密钥
  6. Tips--Ubuntu16.04系统安装时无法连接WiFi
  7. 7-20 打印九九口诀表 (15 分)
  8. 7-160 找完数 (20 分)
  9. 7-69 超市促销 (6 分)
  10. 带头结点的链式表操作集