昨天透過觀察真實舞者得到動態特徵,讓我思考如何改善虛擬舞者機械式的舞蹈動作。
今天,我要與AI夥伴正式把這些「舞蹈靈魂」實作進我們的 3D Tutting 模擬器裡面!
本次新增 32 種 專業緩動曲線(Easing Curves)搭配 SVG 曲線視覺化面板,並完整整合了 BPM 時基計時器讓 Tutting 做更加豐富!!!

3D Tutting 模擬器的時間軸編舞面板,現在已經具有動態表現力的「幾何律動核心」,以下針對各個新功能詳細介紹。
一口氣引入了完整的緩動公式庫。除了最基礎的 Linear 與 SmoothStep,更包含了 Sine / Quad / Cubic / Quart / Quint / Expo / Circ / Back / Elastic / Bounce 等 10 大函數家族,各自配備 In/Out/InOut 三種動態,總共 32 種強大曲線!
function easeOutBounce(t){
const n1 = 7.5625, d1 = 2.75;
if (t < 1/d1) return n1*t*t;
if (t < 2/d1) return n1*(t -= 1.5/d1)*t + 0.75;
if (t < 2.5/d1) return n1*(t -= 2.25/d1)*t + 0.9375;
return n1*(t -= 2.625/d1)*t + 0.984375;
}
const EASINGS = {
linear: t => t,
smoothStep: t => t*t*(3 - 2*t),
easeInSine: t => 1 - Math.cos((t*Math.PI)/2),
easeOutSine: t => Math.sin((t*Math.PI)/2),
easeInOutSine: t => -(Math.cos(Math.PI*t) - 1)/2,
easeInQuad: t => t*t,
easeOutQuad: t => 1 - (1-t)*(1-t),
easeInOutQuad: t => t < 0.5 ? 2*t*t : 1 - Math.pow(-2*t+2, 2)/2,
easeInCubic: t => t*t*t,
easeOutCubic: t => 1 - Math.pow(1-t, 3),
easeInOutCubic: t => t < 0.5 ? 4*t*t*t : 1 - Math.pow(-2*t+2, 3)/2,
easeInQuart: t => t*t*t*t,
easeOutQuart: t => 1 - Math.pow(1-t, 4),
easeInOutQuart: t => t < 0.5 ? 8*t*t*t*t : 1 - Math.pow(-2*t+2, 4)/2,
easeInQuint: t => t*t*t*t*t,
easeOutQuint: t => 1 - Math.pow(1-t, 5),
easeInOutQuint: t => t < 0.5 ? 16*t*t*t*t*t : 1 - Math.pow(-2*t+2, 5)/2,
easeInExpo: t => t === 0 ? 0 : Math.pow(2, 10*t - 10),
easeOutExpo: t => t === 1 ? 1 : 1 - Math.pow(2, -10*t),
easeInOutExpo: t => t === 0 ? 0 : t === 1 ? 1 : t < 0.5 ? Math.pow(2, 20*t-10)/2 : (2 - Math.pow(2, -20*t+10))/2,
easeInCirc: t => 1 - Math.sqrt(1 - Math.pow(t, 2)),
easeOutCirc: t => Math.sqrt(1 - Math.pow(t-1, 2)),
easeInOutCirc: t => t < 0.5 ? (1 - Math.sqrt(1 - Math.pow(2*t, 2)))/2 : (Math.sqrt(1 - Math.pow(-2*t+2, 2)) + 1)/2,
easeInBack: t => { const c1=1.70158, c3=c1+1; return c3*t*t*t - c1*t*t; },
easeOutBack: t => { const c1=1.70158, c3=c1+1; return 1 + c3*Math.pow(t-1,3) + c1*Math.pow(t-1,2); },
easeInOutBack: t => {
const c1=1.70158, c2=c1*1.525;
return t < 0.5
? (Math.pow(2*t, 2) * ((c2+1)*2*t - c2)) / 2
: (Math.pow(2*t-2, 2) * ((c2+1)*(t*2-2) + c2) + 2) / 2;
},
easeInElastic: t => {
const c4 = (2*Math.PI)/3;
return t === 0 ? 0 : t === 1 ? 1 : -Math.pow(2, 10*t-10) * Math.sin((t*10-10.75)*c4);
},
easeOutElastic: t => {
const c4 = (2*Math.PI)/3;
return t === 0 ? 0 : t === 1 ? 1 : Math.pow(2, -10*t) * Math.sin((t*10-0.75)*c4) + 1;
},
easeInOutElastic: t => {
const c5 = (2*Math.PI)/4.5;
return t === 0 ? 0 : t === 1 ? 1 : t < 0.5
? -(Math.pow(2, 20*t-10) * Math.sin((20*t-11.125)*c5))/2
: (Math.pow(2, -20*t+10) * Math.sin((20*t-11.125)*c5))/2 + 1;
},
easeOutBounce,
easeInBounce: t => 1 - easeOutBounce(1 - t),
easeInOutBounce: t => t < 0.5 ? (1 - easeOutBounce(1 - 2*t))/2 : (1 + easeOutBounce(2*t - 1))/2
};
單純看著緩動曲線的名字(例如 easeInOutQuint)很難直觀想像動作動態,因此增加了 SVG 曲線預覽,在編舞面板上方,當你選擇一種 Easing 時,預覽會即時畫出該曲線的完整波形。
過渡時間從的固定秒數,轉化為「基於 BPM 的音樂時基計時」:
拍數(Beat Count):每個拍點可以獨立設定「1/4 拍」到「4 拍」不等的時長。
毫秒換算:過渡時長將自動換算: Duration(ms)=拍數(60000/BPM)
動態連動:在時間軸選取某個拍點時,Easing 與拍數控制項會同步顯示該拍點的設定並即時套用;沒選取拍點時,控制項則作為「下一個新增拍點」的預設值。

搞懂了 Easing 曲線與 BPM 整合後,我們的模擬器就不再只是個勻速甩手的死板機器人,而是一個真正懂幾何、懂得精準 On Beat、能在拍點瞬間爆發肌肉震盪的 數位 Tutting 舞者!
明天見~~~
請在 Keyframe 拍點時間軸中新增以下三塊功能:
一、32 種 Easing 緩動曲線
- 實作 Linear、SmoothStep,加上 Sine / Quad / Cubic / Quart / Quint / Expo / Circ / Back / Elastic / Bounce
十個家族,每個家族各含 In / Out / InOut 三種,總計剛好 32 種(採用標準 Penner 緩動公式)。
- Back 與 Elastic 系列刻意允許輸出超出 0~1 範圍(overshoot),餵進四元數 slerp 插值後會自然產生
「甩過頭再回彈」的頓點效果,用來表現 tutting 的甩勁動作。
二、SVG 曲線預覽
- 每個 easing 都能即時渲染成小型 SVG 曲線圖。
- 面板上方顯示一個 64×34 的大預覽圖(對應目前選取或即將套用的 easing)。
- 每個拍點的 chip 上額外掛一個 26×15 的迷你縮圖,並加上 0 與 1 的基準虛線,方便一眼看出
overshoot 的幅度。
三、BPM 整合
- 每個拍點可獨立設定「拍數」(範圍 1/4 拍~4 拍)。
- 該拍點到下一拍的過渡時長 = 拍數 × (60000 / BPM),並即時顯示換算後的毫秒數。
- 選取某個拍點時,Easing 與拍數控制項要同步顯示該拍點目前的設定,且變更後立即套用到該拍點
(不需要另外按「更新」鈕,因為這兩項設定不影響角度資料)。
- 若目前沒有選取任何拍點,這組控制項則作為「下一個新增拍點」的預設值。
播放邏輯調整:
- 播放時的線性時間 t 只用來判斷「這一段轉場是否播放完畢」。
- 實際餵給四元數 slerp 的插值進度,改為該拍點自訂 easing 函式算出的 easedT,
而不是原本的線性 t。