為了生成曲線,函數需要通過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 boundssplineIndex = splineIndex % _movementPath.Length;// Calculate the spline positionret = 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工程看到實際效果—最初的視圖使用一個不會追逐飛行軌跡的相機,而是慢慢的環繞這個場景,允許簡單的看到飛行軌跡。