为了生成曲线,函数需要通过4个在沿着重量值在0和1之间的路径上连贯的位置。由于重量在这些2个值之间增加,曲线返回在更远的路径上的坐标。
当所提供的重量值为0,曲线将返回正确的坐标在第二个输入坐标。当所提供的重量值为1,曲线将返回一个准确的坐标在第三个输入位置。然而,所有4个值在这些计算中来使用, 来确保在第二个和第三个位置之间的平滑路径,并同样向前进到下一组运行点。
这个可以使用图表来举例说明,在图所示。如果使用点0,1,2和3来计算曲线,重量在0.0,由此产生的位置会是1。如果重量增加到1.0,曲线坐标轨迹将沿着在点1和点2之间的线,当重量到达1.0,将到达点2。注意,从这个点的,曲线坐标没有返回值在线上,朝点0或点3,即使它们将通过曲线函数:这些外部点仅使用来计算在中心的2个点之间的曲线角度。
一旦重量到达1.0,曲线可以移动到下一组点,进入点1,2,3和4。然后重量再次从0.0增加到1.0后,导致计算的坐标,沿着点2和点3之间的线。
从这组点,它不可能返回在点0和1之间的曲线坐标,或者在点4和5之间(图表中显示灰色),有要处理的路径这些部分的不足外部点。
通过在点到点之间的移动路径,一个平滑曲线可以减少,穿过所有定义的位置。这一切在三维空间也能做的很好。
提示:当定义一个路径,别忘记它将采取完全相同的大量时间在每一个连贯的路径点之间移动。因此,你应该尝试去确保点近似等距。点有不同于其它的大的差距将导致更快的移动,在固定时间间隔内穿过增加的距离,点更接近在一起将导致移动缓慢,因为少距离必须要去移动。
为了创建一个封闭的路径,允许飞机去循环,无缝的回到它最初的点再重新开始,我们必须确保最终的曲线的三个点与三个点的第一个相同。当三个点的重量到达1.0时,曲线坐标将终于到达回正确的在移动路径上的点1的位置,允许整个路径从再次从最开始就可以被跟踪。
我们在PaperPlaneObject代码中实现它,通过存储2个类级变量,一个int值称为_splineIndex,它定义4个点的第一个索引来使用曲线计算;一个浮点值称为_splineWeight,它允许我们来沿着曲线在定义点之间穿过路径。
在Update函数中,我们添加一个小的总额到_splineWeight变量中。如果它到达或超过1.0,我们要将它减1.0,并增加_splineIndex。如果_splineIndex通过移动路径的最后的数组点,它重新设置回开始。这些更新沿着曲线移动飞机,并当它到达它的移动路径的重点后,重新设置回起点。
在这些更新模式下,我们调用GetPlanePosition函数来执行曲线计算,并返回最终飞行坐标。这个函数,期望曲线索引和曲线重量值来作为参数来传递,下面代码将描述。
private Vector3 GetPlanePosition(int splineIndex, float splineWeight) { Vector3 ret; // If the weight exceeds 1, reduce by 1 and move to the next index if (splineWeight > 1) { splineWeight -= 1; splineIndex += 1; } // Keep the spline index within the array bounds splineIndex = splineIndex % _movementPath.Length; // Calculate the spline position ret = Vector3.CatmullRom(_movementPath[splineIndex], _movementPath[(splineIndex + 1) % _movementPathLength], _movementPath[(splineIndex + 2) % _movementPathLength], _movementPath[(splineIndex + 3) % _movementPathLength], splineWeight); return ret; }
这代码首先检查曲线重量是否大于1。如果是,它就减去1并且切换到下一个曲线索引(我们将一会儿看到这个原因)。其次是检查循环曲线索引,是否它超过_movementPath数组项目的界限。
曲线坐标然后会简单的计算,通过传递4个矢量坐标和曲线重量到Vector3.CatmullRom函数。注意,然而,因为我们在曲线索引指上使用指数运算符,如果它们超过数组长度,它们会循环回到最初的开始。这个运算允许我们去实现我们的闭合环路(要求重复前3个点),而不必实际在数组中重复它们:它们只是一开始就重复使用,当到达数组末尾时。
随着有能力手动去计算飞行坐标,我们现在可以设置飞行的坐标和沿着轨迹去平滑移动。这是好的开始,但这是非常明显的可视化问题,当它在运动时:飞行总是对着同一个方向。当然,总是朝着移动的方向(纸飞机通常来说不会侧面飞行的很好)。
幸运的是,这是非常容易让飞行看上去它是正在飞行的。我们需要做的第一件事是计算另一个飞行坐标,仅远一点沿着轨迹。我们通过调用GetPlanePosition函数,在1秒钟内来完成它,这个时候添加0.1到曲线重量中。此外的原因是GetPlanePosition函数检查是否重量超过1.0,因为这一秒可以引起溢出的发生。
第二个调用允许我们看到现在在哪里飞行,下一秒后将会在哪里。飞行的方向必须从这些点到下一秒所在的点,因为它的轨迹是移动的。因此我们需要一个旋转飞机的方法以便它面朝从第一个位置到下一个秒所要到的位置。
这个旋转可以使用另一个便利的静态的矩阵函数来完成:CreateWorld。CreateWorld函数创建一个世界矩阵(它是最后我们要在每一个对象的Update方法中尝试去做的)以便它放置在特别的位置上,面对特别的方向。这就是我们所需要的:位置是我们已计算出来的第一条曲线,方向从这到下一条曲线点。
通过从下一个位置减去当前位置来简单计算方向。由此产生的矢量准备作为参数传递给CreateWorld。
有一个小问题任然存在:飞机是持续向一侧飞行的,因为它的一侧已经在SketchUp模式下定义了。要修正这个问题,矩阵计算后,我们简单旋转90度角来旋转它。
计算位置和飞行方向的完整代码如下代码所示。
// Calculate the current position and store in the Position property Vector3 Position = GetPlanePosition(_splineIndex, _splineWeight); // Calculate the next position too so we know which way we are moving Vector3 nextPosition = GetPlanePosition(_splineIndex, _splineWeight + 0.1f); // Find the movement direction Vector3 delta = nextPosition - Position; // Create the world matrix for the plane Transformation = Matrix.CreateWorld(Position, delta, Vector3.Up); // The plane needs to be rotated 90 degrees so that it points // forward, so apply a rotation ApplyTransformation(Matrix.CreateRotationY(MathHelper.ToRadians(-90)));
最终结果是我们有一个平滑的逼真的在房屋间围绕场景飞行。你可以通过运行ChaseCam工程看到实际效果—最初的视图使用一个不会追逐飞行轨迹的相机,而是慢慢的环绕这个场景,允许简单的看到飞行轨迹。