通常在認識網頁最基礎的排版規則之後,馬上就會開始使用 DOM 花花世界的第一步addEventListener
,把 click、mouseenter、wheel 什麼的通通玩他一遍。看到顏色很智障的變來變去就快高潮了。
進入三(Three.js)的世界之後,最基本的互動方式當然還是透過滑鼠與鍵盤,而像我一樣堅持不用滑鼠才是最帥的都是一群怪咖所以先不考慮,當然是滑鼠動來動去就會發生奇怪的事情最棒了!
預計要做的事情,表列出來的話就是:
做完之後就可以讓滾輪互動達到下面這樣的效果!
為了達到偵測滑鼠位置使否在 3D 的石頭物件上面,最直覺想到的就是mouseenter
與mouseleave
。可是 ... 因為 three.js 完全是用 WebGL 所渲染(render)出來的,所以我們再也沒有一個 DOM element 可以粗暴的強行addEventListener
,那該怎麼知道mouseenter
與mouseleave
呢?
三的社群早就考慮到這個常見的情境,設計了一個物件叫做THREE.Raycaster
,實作了 3D 場景中常見的技巧 raycasting。假如這些詞彙都看起來都很機巴的話,相信下面的動畫,看一眼就會懂了!
看來應該是非常的簡單。
第一步,我們先來建立一個 instance,與mouseOnRock
去紀錄狀態:
const raycaster = new THREE.Raycaster();
let mouseOnRock = false;
第二步,直接用一個 object mouseCoords
紀錄滑鼠的在 window 上面的位置,並用mouseMoved
紀錄上一個 frame 到現在這個 frame 之間是否有滑鼠移動 :
function setMouseCoords(x, y) {
const xNew = x / renderer.domElement.clientWidth * 2 - 1;
const yNew = -(y / renderer.domElement.clientHeight) * 2 + 1;
mouseCoords.set(xNew, yNew);
mouseMoved = true;
}
function onDocumentMouseMove(event) {
setMouseCoords(event.clientX, event.clientY);
}
document.addEventListener('mousemove', onDocumentMouseMove, false);
第三步,就是建立每個 frame 呼叫 render 時的偵測函式。完全只要根據 example 的邏輯簡單修改就可以了:
function rayCasterUpdate() {
const { uniforms } = heightmapVariable.material;
if (mouseMoved && rock) {
raycaster.setFromCamera(mouseCoords, camera);
const intersects = raycaster.intersectObject(rock.children[0]);
if (intersects.length > 0) {
mouseOnRock = true;
const { point } = intersects[0];
uniforms.uMousePos.value.set(point.x, point.z);
} else {
mouseOnRock = false;
uniforms.uMousePos.value.set(10000, 10000);
}
mouseMoved = false;
} else {
uniforms.uMousePos.value.set(10000, 10000);
}
}
這樣大功告成啦!
既然已經知道滑鼠跟石頭的關係了,剩下就是 trivial 啦!直接加上 listener 和 callback:
function onMouseWheel(e) {
if (mouseOnRock) {
controls.enabled = false;
const dY = e.deltaY * 0.02;
rockAngle += dY;
rock.rotation.y += dY;
} else {
controls.enabled = true;
}
}
document.addEventListener('wheel', onMouseWheel, false);
比較需要注意的事情是,因為本來後面有塞個THREE.OrbitControls
,方便我們可以用滑鼠來切換場景視角與 zoom。所以,滾輪會啟動後面的 zoom 機制,但是當我已經在石頭上的時候就不想要這樣的機制運作,所以需要偵測:假如mouseOnRock
為真的話,就把controls
關掉,沒有的話就開起來。
來來來,轉動漂亮的旋鈕吧,看到了嗎?枯山水中,旋鈕就是石頭欸哈哈哈!
關於作者
Vibert Thio
致力於將對於技術的深度研究轉化為新型態藝術創作的能量,並思考技術的拓展/侷限與其對於藝術論述/呈現的影響。專長為數位藝術創作、音像程式設計、互動設計,喜愛即時運算的臨場感與不可預測。