iT邦幫忙

2022 iThome 鐵人賽

DAY 6
2

昨天我們講到靠近演算法,應用這個演算法,可以讓一個數值以一個速率持續接近目標值,套用在畫面上的元件,就能將原本死硬的畫面變成滑順的視覺效果。

那麼我們能用同樣的方法來處理旋轉角度嗎?

/** 靠近演算法 */
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(順時鐘),而不是選擇比較近的方向去追(逆時鐘),這樣錯誤的選擇顯然會讓角度的『靠近』表現得不自然。
degree_follow_problem

正確的角度靠近法

首先不要各別去看更新前的角度和目標角度,我們應該去看兩個角度的角度差,然後不管角度的差值到哪裏去,我們都把它繞回-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;
}

用這樣的方法,可以正確地把角度拉向目標,套用至動畫的時候,就會順眼多了。

CG示範專案


上一篇
Trick 4: 你可以再靠近一點…演算法
下一篇
Trick 6: 顏色的靠近演算法
系列文
30個遊戲程設的錦囊妙計32
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

0
酷可
iT邦新手 4 級 ‧ 2022-09-08 18:44:13

角度調整的部分,不知道改成下面這個算法會不會比較好,因為我想說如果 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 來取餘數看看,結果好像可行,就上來分享看看~

小哈片刻 iT邦研究生 4 級 ‧ 2022-09-08 18:57:33 檢舉

很棒喔!
謝謝你的分享~

我要留言

立即登入留言