iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 22
0

source code

0. 今日工事

  • 石頭山蹦出
  • TWEENING:流暢動畫的驅動引擎
  • 造山運動

1. 石頭山蹦出

若是想要產生 3D 的石頭,立馬會想到兩種分別的途徑:

  1. 載入現有的 3D 模型
  2. 自己算(procedural generation)

很明顯的,第一個花的時間相較於第二個一定少很多。既然現在 portotype 就先以快速達到目標為主!

1.1 引入 MTLLoader 與 OBJLoader

上次已經有用 Three.js 的 example 練習過,不過當時使用的方法是直接載範例來改,所以只有動動參數和插入一點點材質改動。現在 karesansui 已經放到了整個 module system 裡面(詳情看 source code),不能像之前一樣<script>一堆東西讓他跑。

剛開使遇到這個問題,我就直接去找尋 es6 版本的 Three.js 的 Object Loader 和 Material Loader。很容易就搜尋到了他們,分別是 three-mtl-loaderthree-object-loader

但是因為是小的開發者自行移植的工具,所以 dependencies 並沒有時常更新,issue 很多也都卡著沒解掉。dependencies 改完之後,實際使用上面卻遇到很多狀況,像是.mtl檔案可以 load 進來,但是和 .obj卻沒有接好,有些地方爛掉。

最後決定使用直接 import 的方式,但是又不想要直接去複製貼上。所以就找到這個 gist。透過設定 alias 和可以去 importnode_modules/three裡面的程式碼。因為程式碼都是直接加在THREE的 field 裡面,所以 import 的方式需要特別注意:

webpack.common.config.js

resolve: {
	alias: {
        // 可以自行設定常用到的資料夾
        'three/loaders': path.join(__dirname, 'node_modules/three/examples/js/loaders'),
        'three/controls': path.join(__dirname, 'node_modules/three/examples/js/controls'),
		// ...
	}
},

//...

plugins:[
	new webpack.ProvidePlugin({
		'THREE': 'three'
	}),
	//...
]

3D 場景的index.js

import 'three';
import 'three/loaders/OBJLoader';
import 'three/loaders/MTLLoader';
import 'three/controls/TrackballControls';
import 'three/controls/OrbitControls';

console.log(THREE.OBJLoader);

接下來照著 example 或是上次經驗一樣的步驟就可以載入模型了!

1.2 自己算呢?(procedural generation)

2. TWEENING:流暢動畫的驅動引擎

什麼是 tweening,根據來歷不明網站的隨意定義

Short for in-betweening, the process of generating intermediate frames between two images to give the appearance that the first image evolves smoothly into the second image.

in-betweening,就是介於兩個狀態:起始與結束之間。而動畫上,常常會使用 keyframes 定義畫面中特定時間點的樣子,之間就用 tweening 的效果去填滿。在底層的程式上,概念更為簡單,其實就是一個變數在一定時間裡面如何 從 0 變為 1的效果:

除了上述線性與指數的變化,還可以設計許多更調皮的動態:

這個數字雖然是簡單的 0 ~ 1,但是只要經過運算,就可以達到無限可能的區間變化。像是驅動石頭的移動與轉動,或是地板紋路的浮現等等。

常見 library 與抉擇

市面上常見的 JavaScript tweening library 有兩個:

  1. tweenjs/tween.js
    • repo
    • npm
    • 5030 stars
    • 5722 downloads in last month
  2. CreateJS/TweenJS
    • repo
    • npm
    • 2535 stars
    • 75 downloads in last month

CreateJS/TweenJS 是在 CreateJS 底下的子專案。因為規模比較大型,連 event 處理的程式碼都是與其他工具共用。這也還好,但我比較擔心的是他很久沒更新了,之前使用過的經驗又遇到很多怪 bug,非常不流暢。也難怪下載量如此的低。

相較之下,第一個 tweenjs/tween.js 比較輕量,src 根本就只有一個檔案,一切都很簡單易懂容易看,我又常常用。無懸念。實際使用的概念如下(值得說一下的事情是,其實兩個套件的 API 設計如出一徹):

// Start at (0, 0)
var coords = { x: 0, y: 0 };

// Create a new tween that modifies 
var tween = new TWEEN.Tween(coords)'coords'.
    // Move to (300, 200) in 1 second.
    .to({ x: 300, y: 200 }, 1000)
    // Use an easing function to make the animation smooth.
    .easing(TWEEN.Easing.Quadratic.Out)
    // Start the tween immediately
    .start();

// Setup the animation loop.
function animate(time) {
    requestAnimationFrame(animate);
    // 記得要設定 update,TWEEN 內部才能跑起來
    TWEEN.update(time);
}
requestAnimationFrame(animate);

3. 造山運動

再來就是實際控制了!

3.1 rotate

先來實做一個點擊旋轉的功能!

demo site

只要點擊石頭就會可愛的轉動喔!

// 已經把模型的 object 載入 rock 這個變數當中
let rockAngle = 0;
let rockRotateAni;

function initAnimations() {
  // 設定 tween 操控的變數
  const rockRotate = { value: 0 };

  rockRotateAni = new TWEEN.Tween(rockRotate)
    // 選擇一個會有點回彈的效果
    .easing(TWEEN.Easing.Back.Out)
    // rockRotate.value 會從 0 跑到 1
    .to({ value: 1 }, 900)
    .onUpdate((rockRotate) => {
      // 這個 callback 的第一個 argument 就是變化中的 rockRotate
      const rate = rockAngle + Math.PI * (rockRotate.value * 0.5);
      rock.rotation.y = rate;
    })
    .onComplete(() => {
      // 到達終點之後,將終點設為下次的起點
      rockAngle = rock.rotation.y;
      // 將 rockRotate 回歸預設
      rockRotate.value = 0;
    });
}
...

// in the callback of click on rock event
rockRotateAni.start();

3.2 造山

講到這,應該可以很好想像下面的山是怎麼浮現與消失的了吧!

demo site

只要按空白鍵就可以讓山消失/出現

一樣的邏輯跟手法,只是改動的參數換成rock.position。但是有一個不一樣的地方:我的構想裡面,石頭的出現與消失應該是一連串的動作。消失的 Tween 是rockPositionAni,在他結束之後,重新浮現的 Tween rockPositionAniBack回直接銜接執行開始呼叫start。tweenjs 已經有設計一個 API 達到這樣的效果,就是chain

// Rocks
let rockPosition;
const rockScale = 70;
const rockPositionY = -11;
let rockScaleAni;
...

const position = { value: 1 };
const rockPositionAniBack = new TWEEN.Tween(position)
  .easing(rockEasingOut)
  .to({ position: 1 }, 400)
  .onUpdate((position) => {
    rock.position.setY(lerp(
      1, 0,
      rockPositionY,
      rockPositionYDisplace,
      position.value));
    });

const rockPositionAni = new TWEEN.Tween(position)
  .easing(rockEasingIn)
  .to({ value: 0 }, 900)
  .onUpdate((position) => {
    rock.position.setY(lerp(
      1, 0,
      rockPositionY,
      rockPositionYDisplace,
      position.value));
  })
  // 在結束之後馬上執行下一個
  .chain(rockScaleAniBack);

4. 請愛CYBER の audio / VISUAL

附上 source code

對於視覺上的 prototype 已經到一個階段了!當然還有很多可以玩的東西,不過既然主題是「CYBER の audio / VISUAL」,就應該把音響開動了,你說是吧?

關於作者

Vibert Thio

致力於將對於技術的深度研究轉化為新型態藝術創作的能量,並思考技術的拓展/侷限與其對於藝術論述/呈現的影響。專長為數位藝術創作、音像程式設計、互動設計,喜愛即時運算的臨場感與不可預測。


上一篇
§d21§ webの 禪師!花紋數學家!棋盤、小溪與潛動!
下一篇
§d23§ 庭院聲幾許?木魚的冥想!Three.js 裝備 Tone.js 前的基本互動!
系列文
aesthEtic,CYBERの audio / VISUAL,網頁中的聲音與影像研究30

尚未有邦友留言

立即登入留言