[UIデザイナーが学ぶ]Three.jsの Raycasterで マウスイベントを使う話
はじめに
この記事はthreejs journeyを利用しての学習メモです。
このthreejs journeyはthree.jsを学ぶのに非常に有用ですので、是非視聴してみてください。おすすめです!!
Raycasterとは
Three.jsで光線(Ray)を使ってオブジェクトとの交差を検出するためのものです。これを利用することで、特定の方向に向かって投げられた光線がシーン内のどのオブジェクトと交差するかを判定できます。
この機能は、例えばクリックした位置にあるオブジェクトを特定したり、オブジェクト同士の衝突を検出したりするのに利用できます。
やってみる
Raycasterの基本的な使い方です。
Raycasterのインスタンスを作成
Raycasterインスタンスのsetメソッドを利用して、原点の位置、Rayの向きを設定。
Rayの向きは正規化しておくintersectObject、intersectObjectsメソッドを利用して対象がRayと交差しているか検出する
const raycaster = new THREE.Raycaster();
// 原点を決める
const rayOrigin = new THREE.Vector3(-3, 0, 0);
// 向きを決める
const rayDirection = new THREE.Vector3(10, 0, 0);
// 正規化する
rayDirection.normalize();
// セット
raycaster.set(rayOrigin, rayDirection);
const intersect = raycaster.intersectObject(object);
対象が複数ある場合。
raycaster.intersectObjects([object1, object2, object3]);
交差の結果は、1 つのオブジェクトだけをテストしている場合でも、常に配列になります。これは、光線が同じオブジェクトを複数回通過する場合があるからです。
Torusを以下のように通過した場合は、2回通過しますので結果は複数になります。
◎注意
注意点としては、レイキャストする前に、オブジェクトを更新しないと正しい結果が得られない場合があります。
// 座標の更新を行う
object.updateMatrixWorld();
const raycaster = new THREE.Raycaster();
const rayOrigin = new THREE.Vector3(-3, 0, 0);
const rayDirection = new THREE.Vector3(10, 0, 0);
rayDirection.normalize();
raycaster.set(rayOrigin, rayDirection);
const intersect = raycaster.intersectObject(object);
Raycasterとマウスイベント
マウスを利用した方法です。
こちらのやり方のほうが良く利用されるかもしれません。
Three.jsの公式ドキュメントのCode Exampleもこちらの方法が紹介されています。
マウスの座標と連携する
raycaster.setFromCameraを利用します。
raycaster.setFromCameraは、Three.jsでカメラとマウスの位置を基に光線を設定するためのメソッドです。
raycaster.setFromCamera(mouse, camera);
mouse
スクリーン座標系でのマウスの位置を示すベクトルです(通常、xとyの値は-1から1の範囲)camera
シーンをレンダリングするために使用されるカメラオブジェクト
const raycaster = new THREE.Raycaster();
const mouse = new THREE.Vector2();
window.addEventListener("mousemove", (event) => {
mouse.x = (event.clientX / sizes.width) * 2 - 1;
mouse.y = -(event.clientY / sizes.height) * 2 + 1;
});
const clock = new THREE.Clock();
const tick = () => {
const elapsedTime = clock.getElapsedTime();
//
raycaster.setFromCamera(mouse, camera);
const objectsTest = scene.children.filter(
(child) => child instanceof THREE.Mesh
);
// 交差をテスト
const intersects = raycaster.intersectObjects(objectsTest);
// 一旦全てのオブジェクトを赤にする
for (const object of objectsTest) {
object.material.color.set("#ff0000");
}
// 交差した場合はオブジェクトの色を青にする
for (const intersect of intersects) {
intersect.object.material.color.set("#0000ff");
}
// Render
renderer.render(scene, camera);
// Call tick again on the next frame
window.requestAnimationFrame(tick);
};
tick();
mouse enterとmouse leaveイベント
3次元空間だとmouse enter、mouse leaveイベントを取得することが難しいです。擬似的ですが以下の方法でmouse enter、leaveのタイミングを取得することができます。
const clock = new THREE.Clock();
let currentIntersect = null;
const tick = () => {
const elapsedTime = clock.getElapsedTime();
raycaster.setFromCamera(mouse, camera);
const objectsTest = scene.children.filter(
(child) => child instanceof THREE.Mesh
);
const intersects = raycaster.intersectObjects(objectsTest);
for (const object of objectsTest) {
object.material.color.set("#ff0000");
}
for (const intersect of intersects) {
intersect.object.material.color.set("#0000ff");
}
// ここを追加
if (intersects.length) {
if(currentIntersect === null) {
console.log("mouse enter")
}
currentIntersect = intersects[0];
} else {
if(currentIntersect) {
console.log("mouse leave")
}
currentIntersect = null;
}
// Render
renderer.render(scene, camera);
// Call tick again on the next frame
window.requestAnimationFrame(tick);
};
tick();
clickイベント
上記で取得したcurrentIntersectを利用してオブジェクトへのclickイベントも取得してみます。
window.addEventListener("click", () => {
if (currentIntersect) {
if (currentIntersect.object === object1) {
console.log("object1");
}
if (currentIntersect.object === object2) {
console.log("object2");
}
if (currentIntersect.object === object3) {
console.log("object3");
}
}
});
まとめ
Raycasterを使いこなさないと、3Dを利用したインタラクティブなコンテンツは作れなそうです。
基礎的な部分はそこまで難しくないのでまずは使ってみて覚えていこうと思います。