クモのようにコツコツと

フロントエンドエンジニア イイダリョウの技術ブログ。略称「クモコツ」

【Three.js】OrbitControls.jsでカメラの位置をインタラクティブに動かす!

Three.jsの続きです。前回は三角関数を使ってカメラの動きを制御しました。今回はさらにカメラの位置をインタラクティブに操作できるようにします。Three.jsの拡張ライブラリ「OrbitControls.js」を使います。それでは行きましょう!

【目次】

※参考:【Three.js】カメラ制御を実現している「三角関数」を理解する!(サイン、コサイン、タンジェント) - クモのようにコツコツと

※Three.jsを習得するためにやったことまとめ
qiita.com

前回作ったもの

前回、ics.Mediaの「Three.js入門」の「カメラの制御」を参考に進めた。

※参考:Three.jsのカメラの制御 - ICS MEDIA

作ったものこちら。

See the Pen three.js camera-1 by イイダリョウ (@i_ryo) on CodePen.

イイダ星と周りの星屑が一緒に回転しているので、イイダ星自体ではなくカメラが回っていることがわかる。

詳細は前回記事を参照

※参考:【Three.js】カメラ制御を実現している「三角関数」を理解する!(サイン、コサイン、タンジェント) - クモのようにコツコツと

この時は初めて*1出会った三角関数の理解がメインになった。

実は、記事にはまだ続きがある。カーソルの位置に合わせてカメラが回転する向きと速度が変わるバージョンがある。今回はまずここからトライ。

カーソルの位置に合わせてカメラが回転する向きと速度を変える

作ったもの

See the Pen three.js camera-2 by イイダリョウ (@i_ryo) on CodePen.

マウスを左右に動かすとカメラが回転する向きと速度が変わる。(スマホだと変わらないぽい)

JSコード全体

// ページの読み込みを待つ
window.addEventListener('load', init);

function init() {

  // サイズを指定
  const width = 960;
  const height = 540;
  let rot = 0;
  let mouseX = 0; // マウス座標

  // レンダラーを作成
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('#myCanvas')
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);

  // シーンを作成
  const scene = new THREE.Scene();

  // カメラを作成
  const camera = new THREE.PerspectiveCamera(45, width / height);

  // ジオメトリを作成(球体)
  const geometry = new THREE.SphereGeometry(300, 30, 30);
  // 画像を読み込む
  const loader = new THREE.TextureLoader();
  const texture = loader.load('https://s.cdpn.io/profiles/user/305282/512.jpg');
  // マテリアルを作成
  const material = new THREE.MeshStandardMaterial({map: texture});
  //メッシュを作成
  const mesh = new THREE.Mesh(geometry, material);
  //シーンにメッシュを適用
  scene.add(mesh);

  // 平行光源
  const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
  directionalLight.position.set(1, 1, 1);
  // シーンに追加
  scene.add(directionalLight);


  // 星屑を作成 (カメラの動きをわかりやすくするため)
  createStarField();

  function createStarField() {
    // 形状データを作成
    const geometry = new THREE.Geometry();
    for (let i = 0; i < 1000; i++) {
      geometry.vertices.push(
        new THREE.Vector3(
          3000 * (Math.random() - 0.5),
          3000 * (Math.random() - 0.5),
          3000 * (Math.random() - 0.5)
        )
      );
    }
    // マテリアルを作成
    const material = new THREE.PointsMaterial({
      size: 10,
      color: 0xffffff
    });

    // 物体を作成
    const mesh = new THREE.Points(geometry, material);
    scene.add(mesh);
  }

  // マウス座標はマウスが動いた時のみ取得できる
  document.addEventListener('mousemove', event => {
    mouseX = event.pageX;
  });
  
  tick();

  // 毎フレーム時に実行されるループイベントです
  function tick() {
    // マウスの位置に応じて角度を設定
    // マウスのX座標がステージの幅の何%の位置にあるか調べてそれを360度で乗算する
    const targetRot = (mouseX / window.innerWidth) * 360;
    // イージングの公式を用いて滑らかにする
    // 値 += (目標値 - 現在の値) * 減速値
    rot += (targetRot - rot) * 0.02;

    // ラジアンに変換する
    const radian = (rot * Math.PI) / 180;
    // 角度に応じてカメラの位置を設定
    camera.position.x = 1000 * Math.sin(radian);
    camera.position.z = 1000 * Math.cos(radian);
    // 原点方向を見つめる
    camera.lookAt(new THREE.Vector3(0, 0, 0));
    
     // 地球は常に回転させておく
    mesh.rotation.y += 0.005;
    
    // レンダリング
    renderer.render(scene, camera);    
    requestAnimationFrame(tick);
  }
}

変更点

変更点を部分ごとに見ていく。

まず、マウス座標の変数を設定

let mouseX = 0; // マウス座標
  • 変数mouseXを設定。初期値は0

次にマウスムーブのイベントを設定

  // マウス座標はマウスが動いた時のみ取得できる
  document.addEventListener('mousemove', event => {
    mouseX = event.pageX;
  });
  • mousemoveイベントを設定。引数名はevent
  • mouseXの値をpageX

pageXはJS組み込みのプロパティ

pageX変数には、マウスイベント発火時の水平位置のピクセル数値が格納されます。 この値はドキュメント全体からの相対位置になります。 このプロパティは、ページの水平スクロールを考慮します。

※参考:.pageX | JavaScript 日本語リファレンス | js STUDIO

マウスイベント時のページの中での水平位置を取得する。

最後、tick()メソッドの中

 function tick() {
    // マウスの位置に応じて角度を設定
    // マウスのX座標がステージの幅の何%の位置にあるか調べてそれを360度で乗算する
    const targetRot = (mouseX / window.innerWidth) * 360;
    // イージングの公式を用いて滑らかにする
    // 値 += (目標値 - 現在の値) * 減速値
    rot += (targetRot - rot) * 0.02;

    // 中略
    
     // 地球は常に回転させておく
    mesh.rotation.y += 0.005;
    
    // 中略  
  }
}
  • 変数targetRotmouseXinnerWidthで割って360を掛ける
  • rottargetRotからrotを引いて、0.02を掛ける
  • meshy方向に0.005ずつ回転する

innerWidthはウィンドウ幅のサイズを取得する。

※参考:window.innerWidth - Web API | MDN

変数`targetRot` = マウス横位置 / ウィンドウ横幅 * 360

targetRotrotの差分に0.02を掛けた数値をrotに加算する。これでマウスの動きとカメラの回転が関連付く。

それとは別で、イイダ星meshは1フレーム0.005ずつ回転させる。

OrbitControls.jsを使ってカメラの動きを360度インタラクティブに動かす

次にカメラの動きを360度グリングリン動かす!これがics.Media「Three.js入門」の「入門編」のラストを飾る部分!

※参考:Three.jsのOrbitControlsで手軽にカメラを制御する - ICS MEDIA

作ったもの

See the Pen three.js camera-3 by イイダリョウ (@i_ryo) on CodePen.

マウスを動かすと星の向きが同期して360度回転する!(こちらはスマホでも動いた!)

JSコード(全体)

// ページの読み込みを待つ
window.addEventListener('load', init);

function init() {

  // サイズを指定
  const width = 960;
  const height = 540;

  // レンダラーを作成
  const renderer = new THREE.WebGLRenderer({
    canvas: document.querySelector('#myCanvas')
  });
  renderer.setPixelRatio(window.devicePixelRatio);
  renderer.setSize(width, height);

  // シーンを作成
  const scene = new THREE.Scene();

  // カメラを作成
        const camera = new THREE.PerspectiveCamera(45, width / height);
        // カメラの初期座標を設定
        camera.position.set(0, 0, 1000);

        // カメラコントローラーを作成
        const controls = new THREE.OrbitControls(camera);

        // 滑らかにカメラコントローラーを制御する
        controls.enableDamping = true;
        controls.dampingFactor = 0.2;

  // ジオメトリを作成(球体)
  const geometry = new THREE.SphereGeometry(300, 30, 30);
  // 画像を読み込む
  const loader = new THREE.TextureLoader();
  const texture = loader.load('https://s.cdpn.io/profiles/user/305282/512.jpg');
  // マテリアルを作成
  const material = new THREE.MeshStandardMaterial({map: texture});
  //メッシュを作成
  const mesh = new THREE.Mesh(geometry, material);
  //シーンにメッシュを適用
  scene.add(mesh);

  // 平行光源
  const directionalLight = new THREE.DirectionalLight(0xFFFFFF);
  directionalLight.position.set(1, 1, 1);
  // シーンに追加
  scene.add(directionalLight);


  // 星屑を作成 (カメラの動きをわかりやすくするため)
  createStarField();

  function createStarField() {
    // 形状データを作成
    const geometry = new THREE.Geometry();
    for (let i = 0; i < 1000; i++) {
      geometry.vertices.push(
        new THREE.Vector3(
          3000 * (Math.random() - 0.5),
          3000 * (Math.random() - 0.5),
          3000 * (Math.random() - 0.5)
        )
      );
    }
    // マテリアルを作成
    const material = new THREE.PointsMaterial({
      size: 10,
      color: 0xffffff
    });

    // 物体を作成
    const mesh = new THREE.Points(geometry, material);
    scene.add(mesh);
  }
 
  tick();

  // 毎フレーム時に実行されるループイベントです
  function tick() {

     // 地球は常に回転させておく
    mesh.rotation.y += 0.005;
    
    // カメラコントローラーを更新
    controls.update();

    // レンダリング
    renderer.render(scene, camera);    
    requestAnimationFrame(tick);
  }
}

OrbitControls.jsを読み込む

この動きはTHREE.OrbitControlsというクラスで実現できる。

※参考:three.js docs

そのためにはThree.js本体とは別に拡張ライブラリの「OrbitControls.js」が必要。

※参考:three.js/OrbitControls.js at master · mrdoob/three.js · GitHub

このように読み込む。

<script src="js/controls/OrbitControls.js"></script>

CodePenで読み込むためにCDNを探した。jsDelivrでCDNを探す方法はこちらがわかりやすい!

※参考:jsDelivrなら超簡単に使いたいライブラリ(プラグイン)のCDNが見つかるかも!|shun|note

下記が見つかった。

※参考:three-orbitcontrols CDN by jsDelivr - A CDN for npm and GitHub

このファイルのリンクパスは下記

https://cdn.jsdelivr.net/npm/three-orbitcontrols@2.110.3/OrbitControls.js

しかしこのパスをリンクしてもエラーになって動かない。デベロッパーツール では下記のエラーコード。

ReferenceError: Can't find variable: require

冒頭にrequire('three')があり、これはNode系のコードっぽい。

var THREE = require('three')

さらに調べるとこちらのQ&Aに別のパスがあり、

※参考:Is there a site to load THREE library? ( No hotlinking ) - Questions - three.js forum

「OrbitControls.js」単体ではなくThree.js本体のファイル一式*2

※参考:three CDN by jsDelivr - A CDN for npm and GitHub

「examples」の中にOrbitControls.jsがあった!

three CDN by jsDelivr - A free, fast, and reliable Open Source CDN

こちらのURLをCodePenにリンクしたらエラーがなくなった!

https://cdn.jsdelivr.net/npm/three@0.101.1/examples/js/controls/OrbitControls.js

変更部分

まずカメラの設定を追加

 // カメラを作成
        const camera = new THREE.PerspectiveCamera(45, width / height);
        // カメラの初期座標を設定
        camera.position.set(0, 0, 1000);

        // カメラコントローラーを作成
        const controls = new THREE.OrbitControls(camera);

        // 滑らかにカメラコントローラーを制御する
        controls.enableDamping = true;
        controls.dampingFactor = 0.2;
  • cameraオブジェクトのpositionプロパティのset()メソッドを設定。引数は左から0、0、1000に
  • 変数controlsTHREE.OrbitControlsインスタンスを作成。引数はcamera
  • controlsオブジェクトのenableDampingプロパティ の値をtrue
  • controlsオブジェクトのdampingFactorプロパティ の値を0.2に

そして最後のtick()に追記

 function tick() {

     // 中略
    
    // カメラコントローラーを更新
    controls.update();

    // 中略
  }
  • controlsオブジェクトのupdate()メソッドを実行

おお、この1行だけであのグリングリンした動きが実現されるの??こりゃすごい!

.update () : Boolean
Update the controls. Must be called after any manual changes to the camera's transform, or in the update loop if .autoRotate or .enableDamping are set.

.update():ブール
コントロールを更新します。 カメラの変換を手動で変更した後、または.autoRotateまたは.enableDampingが設定されている場合は更新ループで呼び出す必要があります。

※参考:three.js docs

最後に

f:id:idr_zz:20200502214501j:plain

ということで今回はカメラがマウスの動きに反応するインタラクティブな動きを実現できました。前回は三角関数の式を細かく書きましたがOrbitControls.jsを使うとグッと少ないコードでもっと複雑な動きが実現できました!

しかし、この動きがどうやって実現しているのかを想像する上でも、前回のような細かい処理を書いていく経験(車輪の再発明)も必要だなと感じました。車輪の再発明とライブラリの恩恵、両方を体験し、比較することでより有意義な使い方をしていきたいと思います。

ics.Mediaの「Three.js入門」、次回からは「基礎編」に入っていきます。それではまた!


※Three.jsを習得するためにやったことまとめ
qiita.com

*1:学生時代にやっているはずだがほぼリセットされている

*2:Three.jsのGitHubと同じアカウントなので公式と思う