在第一篇网络分解成点,线,面.第二篇分别点以球形,线以圆柱,面分别以MergerBatch整合批次显示.因为整合批次显示后,相应的点,线,面不能以Ogre本身的射线来选取,因为整合后,以点举例,多个点显示虽然不在一起,但是是一个Mesh.Ogre本身的检测只能检测到这里,在我们这不满足要求,相应的点,线,面检测都需要自己来计算.

  在讲解本文之前,先看下射线的相关生成代码,只有先明白射线如何生成,生成最后是相对什么空间.

        [OgreVersion( , , , "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 ) & ) == )
{
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 );
}

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), v, ray2.Origin);
}
return Tuple.Create(false, (Real), (Real), 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[]];
var p2 = this.Parent.LineNode.FullTransform * this.Parent[line.Index[]];
//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 >= && 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[]];
var p2 = matrix * this.Parent[tri.sharedVertIndex[]];
var p3 = matrix * this.Parent[tri.sharedVertIndex[]]; 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 away
var 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 positive
var 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 point
var 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[].GetDistance(node.DerivedPosition); }

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

  鼠标移动的代码:

        public override void MouseMove(MouseEventArgs e)
{
int offsetX = e.X - prePoint.X;
int offsetY = e.Y - prePoint.Y;
if (offsetX == && offsetY == )
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(, , 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实现一些功能.

  

Axiom3D:Ogre射线与点,线,面相交,鼠标操作3维空间.的更多相关文章

  1. Axiom3D:Ogre动画基本流程与骨骼动画

    在Axiom中,Animation类用于管理动画,在此对象中主要管理着AnimationTrack对象,此对象用于管理动画的各种类型的每一桢.在Axiom中,动画类型主要有变形动画,姿态动画,骨骼动画 ...

  2. Axiom3D:Ogre公告板集与合并批次

    在上文中,我们把Ogre里的网格分解成点线面后,我们要完成一个新的功能,在点上突出显示. 得到顶点位置后,这个功能也就是一个很简单的事,最开始是每个顶点添加一个子节点,节点上添加一个圆点. forea ...

  3. Axiom3D:Ogre中Mesh网格分解成点线面。

    这个需求可能比较古怪,一般Mesh我们组装好顶点,索引数据后,直接放入索引缓冲渲染就好了.但是如果有些特殊需要,如需要标注出Mesh的顶点,线,面这些信息,以及特殊显示这些信息. 最开始我想的是自己分 ...

  4. Axiom3D:Ogre地形组件代码解析

    大致流程. 这里简单介绍下,Axiom中采用的Ogre的地形组件的一些概念与如何生成地形. 先说下大致流程,然后大家再往下看.(只说如何生成地形与LOD,除高度纹理图外别的纹理暂时不管.) 1.生成T ...

  5. Axiom3D:Ogre中Mesh文件格式分析(一)

    在Axiom3D,或者说是Ogre的mesh的文件格式我们可能通过代码反推出相关格式,相关过程本来我是直接写的,后面发现相关流程写完后,我自己都看晕了,然后我就把一些过程用Execl整理出来,发现过程 ...

  6. 7.3---直线是否相交(CC150)

    注意:就算斜率相等,但是,如果截距也相等,那么是属于相交,所以要特殊判断. public class CrossLine { public boolean checkCrossLine(double ...

  7. [NetTopologySuite](1)线面相交

    用DotSpatial.Topology进行的测试,即使有NetTopologySuite类库进行测试: Polygon inputGeometry = null; LineString analys ...

  8. POJ 3304 Segments(线的相交判断)

    Description Given n segments in the two dimensional space, write a program, which determines if ther ...

  9. visio直线交叉相交跨线修改

    在使用visio画流程图时,经常会遇到两条直线相交.下面讲如何修改使得相交点变成我们想要的方式. 可以设置如下: (1)  全局直线相交,设置跨线标志. (2)  对每条线进行相交跨线设置. (一) ...

随机推荐

  1. Xilinx官网查询各个版本软件的手册

    在Xilinx官网查询各个版本软件的手册需要点击 See All Versions

  2. 互斥锁pthread_mutex_init()函数

    linux下为了多线程同步,通常用到锁的概念.posix下抽象了一个锁类型的结构:ptread_mutex_t.通过对该结构的操作,来判断资源是否可以访问.顾名思义,加锁(lock)后,别人就无法打开 ...

  3. GPG key retrieval failed

    Total size: 340 k Installed size: 1.2 M Is this ok [y/N]: y Downloading Packages: warning: rpmts_Hdr ...

  4. at org.apache.catalina.webresources.CachedResource.validateResources

    "catalina-exec-659" #5239 daemon prio=5 os_prio=0 tid=0x00007fcba8099800 nid=0x581 waiting ...

  5. [Windows Azure] How to Create and Configure SQL Database

    How to Create and Configure SQL Database In this topic, you'll step through logical server creation ...

  6. u3d中的向量 vector3 vector2

    Vector3(x,y,z)x代表左右,y代表上下,z代表前后 Vector3.magnitude 长度 计算两点之间的距离  .如果只给了一点的话.算出的长度其实就是和Vector3.zero点之间 ...

  7. Java获取某个月的天数

    https://blog.csdn.net/wtopps/article/details/48262221 ********************************************** ...

  8. Lintcode: Majority Number 解题报告

    Majority Number 原题链接:http://lintcode.com/en/problem/majority-number/# Given an array of integers, th ...

  9. Ubuntu 安装 mysql

    sudo apt-get install mysql-server sudo apt install mysql-client sudo apt install libmysqlclient-dev

  10. jdbc连接sqlserver报错java.lang.ClassNotFoundException: com.microsoft.jdbc.sqlserver.SQLServerDriver

    使用2008的数据库, 我已经引入的sqljdbc4的包,单还是报这个错,很奇怪突然想到在配置hibernate的时候,是拷贝下来的代码 然后到网上查了下, 因为是2008的版本驱动和2000的有点不 ...