iT邦幫忙

2018 iT 邦幫忙鐵人賽
DAY 23
0

demo

0. 今日工事

  • 基本互動 basic
  • 三的互動:射線交集 Raycaster
  • 實作(一):偵測滑鼠是否在石頭上
  • 實作(二):滾輪轉動石頭

1. 基本互動 basic

通常在認識網頁最基礎的排版規則之後,馬上就會開始使用 DOM 花花世界的第一步addEventListener,把 click、mouseenter、wheel 什麼的通通玩他一遍。看到顏色很智障的變來變去就快高潮了。
進入三(Three.js)的世界之後,最基本的互動方式當然還是透過滑鼠與鍵盤,而像我一樣堅持不用滑鼠才是最帥的都是一群怪咖所以先不考慮,當然是滑鼠動來動去就會發生奇怪的事情最棒了!

預計要做的事情,表列出來的話就是:

  1. 滑鼠移動時,要偵測是否鼠標已經在石頭身上
  2. 假如在石頭身上的時候,上下滾輪就會讓石頭轉動

做完之後就可以讓滾輪互動達到下面這樣的效果!

2. 三的互動:射線交集 Raycaster

為了達到偵測滑鼠位置使否在 3D 的石頭物件上面,最直覺想到的就是mouseentermouseleave。可是 ... 因為 three.js 完全是用 WebGL 所渲染(render)出來的,所以我們再也沒有一個 DOM element 可以粗暴的強行addEventListener,那該怎麼知道mouseentermouseleave呢?

三的社群早就考慮到這個常見的情境,設計了一個物件叫做THREE.Raycaster,實作了 3D 場景中常見的技巧 raycasting。假如這些詞彙都看起來都很機巴的話,相信下面的動畫,看一眼就會懂了!

看來應該是非常的簡單。

3. 實作(一):偵測滑鼠是否在石頭上

第一步,我們先來建立一個 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);
	}
}

這樣大功告成啦!

4. 實作(二):滾輪轉動石頭

既然已經知道滑鼠跟石頭的關係了,剩下就是 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關掉,沒有的話就開起來。

demo

5. 請愛CYBER の audio / VISUAL

source code

來來來,轉動漂亮的旋鈕吧,看到了嗎?枯山水中,旋鈕就是石頭欸哈哈哈!

關於作者

Vibert Thio

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


上一篇
§d22§ webの 禪師!運氣挪心山!動力之枯朽山水!
下一篇
§d24§ 庭院聲幾許?木魚的冥想! Tone.js 載入 mp3 咚咚作響!
系列文
aesthEtic,CYBERの audio / VISUAL,網頁中的聲音與影像研究30

尚未有邦友留言

立即登入留言