昨天我們講到靠近演算法,應用這個演算法,可以讓一個數值以一個速率持續接近目標值,套用在畫面上的元件,就能將原本死硬的畫面變成滑順的視覺效果。
那麼我們能用同樣的方法來處理旋轉角度嗎?
/** 靠近演算法 */
function numberFollowTarget(
current: number, // 目前的數值
target: number, // 想要靠近的目標值
rate: number // 靠近的速率
): number {
// 回傳更新後的數值
return current * (1 - rate) + target * rate;
}
/** 使用靠近演算法來實現旋轉角度的靠近
* 我們將currentDegree以0.5的速率靠近targetDegree
*/
let currentDegree = 0;
let targetDegree = 100;
let newDegree = numberFollowTarget(currentDegree, targetDegree, 0.5);
console.log('靠近一幀後的角度 = ' + newDegree);
// 最後console會列印出下面這一行
// > 靠近一幀後的角度 = 50
感覺很正確呀!有什麼問題嗎?
旋轉角度的問題在於,角度的數值雖然可以無限延伸,但是在幾何上,角度是每360度循環一圈的。也就是說,在數線上我們每隔360切一刀,這些被切下來作為角度的數值區間,在幾何意義上完全等價。
一個角度加上360度,或是減去360度,在幾何意義上是停留在同一個角度的。
如果我們改變一下剛剛旋轉角度的靠近目標,就會發現角度靠近的方向不太對勁。
let currentDegree = 0;
let targetDegree = 300;
let newDegree = numberFollowTarget(currentDegree, targetDegree, 0.5);
console.log('new degree = ' + newDegree);
// 最後console會列印出下面這一行
// > new degree = 150
使用原本的靠近演算法,角度就會繞比較遠的方向去追目標角度300(順時鐘),而不是選擇比較近的方向去追(逆時鐘),這樣錯誤的選擇顯然會讓角度的『靠近』表現得不自然。
首先不要各別去看更新前的角度和目標角度,我們應該去看兩個角度的角度差,然後不管角度的差值到哪裏去,我們都把它繞回-180度到180度的範圍內,只要角度差在這個區間,那麼以這個差值的方向去靠近就不會發生繞得超過半個圓的情況了。
/** 修正過的角度靠近演算法 */
function degreeFollowTarget(
current: number, // 目前的角度
target: number, // 想要靠近的目標角度
rate: number // 靠近的速率
): number {
// 先算出角度差
let diff = target - current;
// 把角度差調整到-180到180的範圍
while(diff > 180) {
diff -= 360;
}
while(diff <= -180) {
diff += 360;
}
// 回傳更新後的角度
return current + diff * rate;
}
用這樣的方法,可以正確地把角度拉向目標,套用至動畫的時候,就會順眼多了。
角度調整的部分,不知道改成下面這個算法會不會比較好,因為我想說如果 diff 的數值差異非常大,像是 75623 之類的(雖然好像不太可能),這樣用 while 感覺會跑很多次,所以想了這個方法。
我有實際開專案測試過,建立兩個指針,分別用小哈教的方式,和我後來改的方式,讓他們一起跑了 10000 次,數值幾乎一樣,雖然有 1051 次的誤差,但那都是在小數點後 12 位以後了,至少畫面上的動畫我的肉眼也看不出差別w
/** 修正過的角度靠近演算法 */
function degreeFollowTarget(
current: number, // 目前的角度
target: number, // 想要靠近的目標角度
rate: number // 靠近的速率
): number {
// 先算出角度差
let diff = target - current;
// 把角度差調整到-180到180的範圍
if (diff > 180) {
diff = (diff + 180) % 360 - 180;
}else if(diff <= -180) {
diff = (diff - 180) % 360 + 180;
}
return current + diff * rate;
}
因為 -180 ~ 180 就是 360,所以我就想說利用 360 來取餘數看看,結果好像可行,就上來分享看看~
很棒喔!
謝謝你的分享~