[翻译]XNA 3.0 Game Programming Recipes之thirtytwo

PS:自己翻译的,转载请著明出处格
                                                5-10 计算在指针和地形:Surface Picking之间的碰撞点
问题
                你想找到准确的3D坐标在你的地形上在这个位置被你的指针所指出。这是必须的,如果你想要在地形上指出大的位置,为你的对象移动到或当布置一个对象在一个地形上时。
解决方案
                做为一个讨论在4-19节,2D点在你的屏幕上,用你的指针所指示,相应的条射线在你的3D屏幕中。在这一节,你将会利用这个射线直到你撞击到地形。
                你可以做这个用一个二进制收索算法,它能够给你一个碰撞的位置用一个精确度的选择。
                鉴于hilliness的地形,它可能有多个碰撞点在射线和地形之间,如图5-20所示。因此,你想在二进制搜索用线性搜索之前,所以你确保检测最接*相机的碰撞。
它是如何工作的
                这个方法转化为2D屏幕指针的位置到3D射线。它充分解释了在4-19节的第一部分。
 1 private Ray GetPointerRay(Vector2 pointerPosition)        
 2 {
 3      Vector3 nearScreenPoint=new Vector3(pointerPosition.X,pointerPosition.Y,0);
 4      Vector3 farScreenPoint=new Vector3(pointerPosition.X,pointerPosition.Y,1);
 5      Vector3 near3DWorldPoint=device.Viewport.Unproject(nearscreenPoint,moveCam.ProjectionMatrix,moveCam.ViewMatrix,Matrix.Identity);
 6      Vector3 far3DWorldPoint=device.Viewport.Unproject(farScreenPoint,moveCam.ProjectionMatrix,moveCam.ViewMatrix,Matrix.Identity);
 7      Vector3 pointerRayDirection=far3DWorldPoint-near3DWorldPoint;
 8      Ray pointerRay=new Ray(near3DWorldPoint,pointerRayDirection);
 9      return pointerRay;
10 }
注意:在这个版本,你不能规格化射线的方向。一会我将会解释。
                现在你知道了指针射线,你已经准备好检测在射线和地形之间的碰撞。
Binary Search
                这个射线包含起始点(在这种情况下,射线穿透了*裁剪*面)和方向。图5-19显示射线和地形。起始点作为A点。射线的方向是一个向量在A点到B点之间。
                总的想法在二进制收索之后是十分直观的。你开始于两个点,A和B,它们可以保证碰撞点在它们之间。在点A和B之间计算这个点去找到他们的中点,它被点1在图5-19所指示。你可以检查这个点是否在地形的上方或下方。如果这个点是在地形的上方(如在这种情况下的图象),你知道碰撞点是在点1和点B之间的。
                接下来。找到点2,在点1和B之间。这个点是在地形的下面,所以你知道碰撞点在点1和点2之间。
                你继续缩小。观察点3,在点1和点2之间。这个点又一次在地形的上面,所以这个碰撞在3和2之间。
                接下来,点4在3和2之间在地形的下面,所以碰撞在3和4之间。
                最后,检查点5,它是在3和4之间的。你发现点5的高度是非常接*地形的高度在(X,Z)位置,所以你已经发现了碰撞点。
Finding Point A and Point B                     
                在准备使用二进制收索,你需要开始于两个点,A和B,在你的射线上,你要确保碰撞点在它们之间。
                您可以放心使用的指针射线起始位置作为A点,因为它是射线上最接*摄象机的点(它是布置在*景剪裁*面;详细参见4-19节)。
                现在,你将会得到这个点它是射线穿透远景*面作为点B。这是一个不坏的选择,因为它是最远的距离点在射线上。仍然可以被摄象机看见。
                作为你能看见的在GetPointerRay方法如4-19所解释的那样,这个pointerRay.Direction相当于你需要同点A到B增长的向量。
The Binary Search Method
                现在你知道了点A到点B的方向了,你已经准备去检测与地形的碰撞了。这个二进制收索算法在前面已经解释过了,转化成伪代码,成为这:
                只要在高度差异之间当前的点和地形下面太大,做下面几点:
                a.分射线的方向一半。
                b.增加结果的方向到当前的点去获得下一个点。
                c.如果下一个点是同样在地形的上面,保存下一个点作为当前的点。
                想象一下,这是走的射线的每一步,在你脚步前面的地方,你检查你的脚步是否在地形之下。这种情况并非如此,你继续同时每一步都步长减半。一旦你放置你的脚步在你的地形下面,你收回你的脚步,并试图减少一半,知道你结束你的脚步正确的在地形的位置上,与地形相碰撞。
                代码如下:
 1 private Vector3 BinarySearch(Ray ray)
 2 {
 3     float accuracy=0.01;
 4     float heightAtStartingPoint=terrain.GetExactHeightAt(ray.Position.X,-ray.Position.Z);
 5     float currentError=ray.Position.Y-heightAtStartingPoint;
 6     while(currentError>accuracy)
 7     {
 8         ray.Direction/=2.0f;
 9         Vector3 nextPoint=ray.Position+ray.Direction;
10         float heightAtNextPoint=terrain.GetExactHeightAt(nextPoint.X,-nextPoint.Z);
11         if(nextPoint.Y>heightAtNextPoint)
12         {
13              ray.Position=nextPoint;
14              currentError=ray.Position.Y-heightAtNextPoint;
15         }
16     }
17     return ray.Position;
18 }
                 你开始计算不同的高度差在其始点在你射线上的和地形之下的点之间。
                 该while将循环播放,直到这种差异小于您预定准确性0.01f 。如果差异仍然较大,您减半步长和计算在射线上的下一点 。如果这个下一个点是在地形之上,你走到这一点,并计算出在这一点高度差。如果没有,什么也不做,以便下一次步长再次减半。
                 在while循环之后,ray.Position将包含一个在射线上的位置,它与地形的高度差小于0.01f
                 这是你怎么样使用这个方法,开始射线AB用创建GetPointerRay:
1 pointerPos=BinarySearch(pointerRay);
注意:如果你的指针没有超过地形,这个方法将永远留在while循环中。因此,它能被用来打破while循环,如果一个对应的价值是比开端大,在这节后面的代码会显示:
Problems with the Binary Search
                在大数情况,二进制搜索会做得很出色,但在某些情况下,它只会一败涂地,草草收场,如图5-20所示
                由于而进制检查不会检查点0和1之间的地形高度,在第一个山岗和射线之间的碰撞是检测不到的,同样的结果(点5)作为射线和地形之间的碰撞返回。
                为了解决这个,二进制搜索应加上线性搜索,这是简单比较二进制搜索。
Linear Search
                在线形收索里,你准备去分你的射线成很多相等的距离,例如,八部分,如图5-21所示。
                你简单的利用你的射线用相同的大小,到你一个点在地形下面为止。这将会不会给你一个准确的结果,但是至少你会检测到射线与第一个山岗相撞了。
                由于在图5-21中点1和点2都不接*地形,你想使用二进制收索在射线上的点1和点2之间去精确的找到碰撞点。
                这LinearSearch方法接收整个射线在A和B之间,分成相等的几份,返回相应碰撞占据的某部分:
 1 private Ray LinearSearch(Ray ray)
 2 {
 3     ray.Direction/=300.0f;
 4     Vector3 nextPoint=ray.Position+ray.Direction;
 5     float heightAtNextPoint=terrain.GetExactHeightAt(nextPoint.X,-nextPoint.Z);
 6     while(heightAtNextPoint<nextPoint.Y)
 7     {
 8          ray.Position=nextPoint;
 9          nextPoint=ray.Position+ray.Direction;
10          heightAtNextPoint=terrain.GetExactHeightAt(nextPoint.X,-nextPoint.Z);
11     }
12     return ray;
13 }
                在这个例子中,射线被划分至少30份。逐渐增多的这个值将会增加检测概率变的最高,但是要求更多的处理能力。
                对于每一个点,你计算下一个点,检查下一个点是否在地形的上面或下面。只要它是在地形的上面,继续。如果下一个点在地形的下面,返回当前的射线包括在碰撞之后的这个点,和发生碰撞的那一份。
                这个射线立即被用来开始BinarySearch的方法:
1 Ray shorterRay=LinearSearch(pointerRay);
2 pointerPos=BinarySearch(shorterRay);
Application-Specific Optimization
                线形收索将确保你检测到小的高峰,同时下面的二进制收索将给你当前你想要的。
                在前面的代码,你已经取得了点A和B作为射线碰撞点,在*景和远景的剪辑*面。这很好,但是如果在*景和远景剪辑*面之间的距离很大,这个射线将同样很大。意思是你将会做大量的没有用的检测在地形上或下的距离。
                相反,你考虑的只是射线能够碰撞地形产生的高度。本书的所用的地形,最大值是30,同时最小值是0,所以它最好能找到在你射线上的点,Y坐标是30,用这个作为起始点A。接下来,用Y=0找到点在你的射线,用这个点做为结束点B。
                由此产生的射线用这个方法构成:
 1 private Ray ClipRay(Ray ray,float highest,float lowest)
 2 {
 3     Vector3 oldStartPoint=ray.Position;
 4     float factorH=-(oldStartPoint.Y-heighest)/ray.Direction.Y;
 5     Vector3 pointA=oldStartPoint+factorH*ray.Direction;
 6     float factorL=-(oldStartPoint.Y-lowest)/ray.Direction.Y;
 7     Vector3 pointB=oldStartPoint+factorL*ray.Direction;
 8     Vector3 newDirection=pointB-pointA;
 9     return new Ray(pointA,newDirection);
10 }
                为了找到这点在射线上用一个特殊Y坐标,找到Y值和初始射线的开始点的Y坐标之间的差值。如果你知道射线的方向的高度差异,你知道射线的哪一部分(保存在factor)你应该增加到开始的点去达到要求Y值的点。
注意:oldStartPoint的Y坐标是正的,同时Y坐标的方向将会为负作为射线的下降。你要factor成为正值,用-标识。
                如果你起始于这个结果的射线,你将会需要去很多检测在你的LinearSearch去结束用同样的峰值检波率。
代码
      参看上述代码!
原文地址:https://www.cnblogs.com/315358525/p/1541349.html