【Three.js】レイキャストに挑戦

JavaScript,Three.js

【こなくそ】
愛媛県周辺の方言。逆境において奮起する意気込みを表す語。
キン肉マンがよく言う。

レイキャストとは

光線を出して、それと交差した対象物を検出する技術のことです。

やりたいこと

電気スタンドのスイッチがクリックされたら照明ON。これをやりたい。

クリックされたものがスイッチかどうかを判定するためにレイキャストを使います。

まずは電気スタンドの3Dモデルを用意します

こちらの動画を参考にしました。ありがとうございます。

blenderおもしろいですね。そんな事までできちゃうの?って感じで。

でも全然わからない…。動画の通り操作しているだけ…。本買ってちゃんと勉強しようかな。

Raycasterクラスを使ってレイキャスト

Three.jsにはあるんですねぇ。レイキャスト用のクラスが。すごい。

レイキャストやってるコードがコチラです↓。

let lamp;                   // 電気スタンド
let lampSwitch = false;     // ON-OFF用フラグ
let lampBulb;               // 電気スタンドの電球
let lampLight;              // 電気スタンド用のライト

//*****************************************
// いろいろ初期化処理
//*****************************************
function init() {
  // 電気スタンド用のライト
  lampLight = new THREE.PointLight(0xffffff);
  lampLight.distance = 8;
  lampLight.intensity = 1.2;
  lamp.add(lampLight);
  lampLight.position.copy(lamp.children[4].position);
  lampLight.visible = false;

  // ONにした時の電気スタンドの電球
  const bulbGeo = new THREE.SphereGeometry(0.35, 64, 64);
  const bulbMat = new THREE.MeshBasicMaterial({color: 0xffffff});
  lampBulb = new THREE.Mesh(bulbGeo, bulbMat);
  lamp.add(lampBulb);
  lampBulb.position.copy(lamp.children[4].position);
  lampBulb.visible = false;

  // イベントリスナー登録
  window.addEventListener("mousedown", turnOnTheLight);
}

//*****************************************
// 電気スタンドON/OFF
//*****************************************
function turnOnTheLight(e) {
  const mouse = new THREE.Vector2();
  mouse.x = (e.clientX / window.innerWidth) * 2 - 1;
  mouse.y = -(e.clientY / window.innerHeight) * 2 + 1;

  const raycaster = new THREE.Raycaster();
  raycaster.setFromCamera(mouse, camera);
  const intersects = raycaster.intersectObjects(scene.children[5].children);

  if (intersects.length > 0) {

    if (intersects[0].object.name == "LampSwitch") {

      if (lampSwitch == false) {

        intersects[0].object.position.y -= 0.1;
        lampSwitch = true;
        lampBulb.visible = true;
        lampLight.visible = true;

      } else {

        intersects[0].object.position.y += 0.1;
        lampSwitch = false;
        lampBulb.visible = false;
        lampLight.visible = false;

      }
    }
  }
}

34~36目でマウスの座標変換を行っています。詳しくは過去の記事をどうぞ。

39行目、RaycasterクラスのsetFromCameraメソッドに変換したマウスの座標とカメラを渡してあげると、マウスから画面奥方向に向かって光線が出ます。見えないけど出てるみたいです。

40行目、intersectObjectsメソッドには、光線との交差をチェックしたいオブジェクト群を配列で渡してあげます。そうすると、そのオブジェクト群の内、光線と交差したオブジェクトが配列として取得できます。

配列には手前から近い順に格納されます。

一番手前のオブジェクトの名前が"LampSwitch"だったら、電気スタンド用のライトをつけたり消したり、ONにした時用の白い電球の表示/非表示を切り替えたり、そんなこんなで出来上がり。

ちょっと補足説明

const intersects = raycaster.intersectObjects(scene.children[5].children);

intersectObjectsメソッドに渡している引数がややこしい事になっているので説明します。

シーン内には以下の6つのオブジェクトが入っています。

  • DirectionalLight
  • AmbientLight
  • テレビ
  • ノート
  • 電気スタンド

この6つがscene.childrenになり、その中の要素番号[5]が電気スタンドになります。要素番号はブラウザのデベロッパーツールで確認できます。

で、その電気スタンドもいくつかのオブジェクトでできているのですが、それがscene.children[5].childrenという配列になります。

今回はその配列の中の"LampSwitch"という名前の付いたオブジェクトを検出したいワケですね。絵にするとこう↓。

ちなみに"LampSwitch"というオブジェクトの名前は、blender側で設定してあげるとThree.js側でもその名前で扱うことができます。

補足説明は以上です。

あとがき

blenderで作った3DモデルをThree.jsで読み込むと、オブジェクトがいっぱいできてて混乱しますが、こなくその精神でもって、デベロッパーツールを使って根気よく調べると分かってくると思います。

あと、オブジェクトの判別をしやすいように、blender側であらかじめ名前をつけておくのも大切かなぁと思いました。今回は以上です。ありがとうございました。

おしゃれ度

★★☆☆☆

Posted by ナカタ