マウスを追ってくるオブジェクトをThree.jsで作ってみた

マウスを追ってくるオブジェクトをThree.jsで作ってみた

前に書いた記事

WebGL Advent Calendar 2018 – Qiita 15日目の記事です(遅刻)

以前こんな記事を投稿しました。

Google PixcelのサイトがかっこよすぎたのでWeb上での3Dオブジェクトの扱い方に少しだけ触れてみた [three.js] – 忘れっぽいLOG
タイトルの通り、Pixcel 3 のサイトがかっこよかったので、Three.jsの使い方を簡単に学んでみるよ!という記事です。
この記事では、Three.js公式の入門記事に従い、回転するキューブを描画するだけのページを作成しました。

ここから本題

今回は、例のサイトにより近づけるため、マウスを追いかけるように回転するキューブを作成しました!
顔がマウスを追ってくるとか、目がマウスを追ってくるとか、そんなことに使えるのでは…多分…

実際に作ったものはここにおいてます

つかったもの

Three.jsとVue.jsを使って実装をしました。

実装方法

まずは、ワイヤーのキューブを描画します。
そもそも「Three.jsがまず全くわからん!」という方はこちら

下準備と初期描画

Vue.jsで処理を行うため、書くオブジェクト、パラメータをまずは宣言します。

export default {
  data () {
    return {
      //3Dオブジェクト描画用のThree.jsのパラメータ
      scene : new THREE.Scene(), 
      camera : new THREE.PerspectiveCamera( 100, window.innerWidth/window.innerHeight, 0.1, 1000 ), 
      renderer : new THREE.WebGLRenderer(), 
      geometry : new THREE.BoxGeometry( 4, 4, 4 ), 
      material : new THREE.MeshBasicMaterial( { color: 0x563d7c, wireframe: true } ),

      canvasWrap : 0,        //描画ターゲットのDOM格納用
      cube : 0,              //正方形のオブジェクト格納用
      delay : 0.001,         //遅延処理用パラメータ(後述)
      loop_counter: 0,       //遅延処理用ループカウンター(後述)
      move_target_x : 0,     //オブジェクトが向くべき角度x
      move_target_y : 0,     //オブジェクトが向くべき角度y
    }
  },

そして、初期描画処理を行います。以下のメソッドはすべてVueのmethodsとして定義します。

<div class="container">
  <div id="canvas_wrap" class="canvas_wrap"></div>
</div>
//初期描画
firstdraw : function(){
  this.cube = new THREE.Mesh( this.geometry, this.material );
  this.cube.position.set(0,0,0);
  this.scene.add( this.cube );
  this.camera.position.z = 10;
  this.canvasWrap = document.getElementById("canvas_wrap");
  this.renderer.setSize( this.canvasWrap.clientWidth, this.canvasWrap.clientHeight);
  this.canvasWrap.appendChild( this.renderer.domElement );
  this.renderer.render( this.scene, this.camera );
  this.animate(); //描画
},
//アニメーション用再描画処理
animate : function(){
  requestAnimationFrame( this.animate )
  this.renderer.render( this.scene, this.camera )
}

これでとりあえずワイヤーの正方形が描画されました。

スクリーンショット 2018-12-16 19.15.21.png

ポインタの座標取得と回転角度の処理

つぎに、ポインタの座標に合わせて、オブジェクトの回転角度を変える処理を作成します。
マウスの座標と、描画するcanvas要素の座標から、実際にキューブが向くべき角度を計算します。

//マウスに追従して回転角度を設定
changeBoxDirection : function(){
  //canvasの座標取得
  var rect = this.canvasWrap.getBoundingClientRect() 

  //回転角度の計算 event.screenX,Yでポインタの座標を取得できます
  var cube_rotation_y = (event.screenX - this.canvasWrap.clientWidth/2 - rect.left) / 360
  var cube_rotation_x = (event.screenY - this.canvasWrap.clientHeight/2 - rect.top) / 360

  // キューブの回転角度に代入
  this.cube.rotation.x= cube_rotation_x
  this.cube.rotation.y= cube_rotation_y
  this.animate(); //描画
},

HTML側には、mousemoveのイベントを取得するよう追記します。

<div class="container">
  <div id="canvas_wrap" class="canvas_wrap" @mousemove="changeBoxDirection()"></div>
</div>

これで、ポインタがcanvas要素上で動くと、オブジェクトがマウスを追従するようになりました。

画面収録 2018-12-16 19.14.32.mov.gif

これでやりたいことはできたのだけど、なにか違う…
もしこのキューブがキャラクターの顔だったら…?

ぼく「遅延が必要なのか!!!」

いい感じに遅延させる

というわけで、キューブの追従に 遅延 を行うように処理を追加します。

遅延処理の考え方

遅延処理のイメージは簡単にこんな感じ
– 現在の角度と、目的の角度に大きく差があれば早く動く
– 目的の角度に近づくほど、ゆっくり動く

そのため、処理としては、 現在の角度と目的の角度の差が回転速度に比例する処理 を作成すればいいことになります。

具体的な計算式はこんな感じ
新しいキューブの角度 = (目的の回転角度 - 現在のキューブの角度) * 遅延定数

この計算をループで呼び出すことで、遅延する角度変更ができるようになります。

ソースに落とし込みます

//マウスに追従して回転角度を設定
changeBoxDirection : function(){
  var rect = this.canvasWrap.getBoundingClientRect() //canvasの座標取得

  //回転角度の計算
  var cube_rotation_y = (event.screenX - this.canvasWrap.clientWidth/2 - rect.left) / 360
  var cube_rotation_x = (event.screenY - this.canvasWrap.clientHeight/2 - rect.top) / 360

  // キューブの角度に代入していたところを、ターゲットへの代入に変更
  this.move_target_x = cube_rotation_x
  this.move_target_y = cube_rotation_y
  this.goDefaultLoop(); //遅延処理用ループの呼び出し
},
//遅延処理のためのループ
goDefaultLoop : function () {
  var self = this
  setTimeout(function () {
    self.goDefaultCalclate() //キューブの角度計算処理の呼び出し
    //無限ループを防ぐためのループ回数を制限
    self.loop_counter++;  
    if (self.loop_counter < 100) { 
      self.goDefaultLoop()
    }else{
      self.loop_counter = 0
    }                      
  }, 60)//60ミリ秒ごとに処理
},  
goDefaultCalclate:function(){
  //次にキューブに加算する角度の初期化
  var velocityX = 0
  var velocityY = 0
  //キューブに加算する角度の計算
  velocityX = (this.move_target_x - this.cube.rotation.x) * this.delay
  velocityY = (this.move_target_y - this.cube.rotation.y) * this.delay
  //キューブの角度に加算
  this.cube.rotation.x += velocityX
  this.cube.rotation.y += velocityY
},

上記の処理を行うことで、キューブが生き物っぽくついてくるようになりました!
再描画処理は常に動き続けているので、this.cube.rotationを変更するだけで描画されます。

おまけ:マウスが外れたら真ん中に戻る

このままだと、ポインターがcanvasから外れてしまうとその角度を向いたままになるので、ポインターが外れたら真ん中に戻る処理を追加します。

mouseoutイベントを追加

<div class="container">
  <div id="canvas_wrap" class="canvas_wrap" @mousemove="changeBoxDirection()" @mouseout="defaultBoxDirection()"></div>
</div>
//マウスが外れたときにもとに戻る処理
defaultBoxDirection : function(){
  //ターゲットを(0,0)に設定
  this.move_target_x= 0
  this.move_target_y= 0
  //遅延用ループの呼び出し
  this.goDefaultLoop();
},

これでマウスが外れたら真ん中に戻るようになりました。

つくってわかったこと、よかったこと

  • Vue.jsとアニメーション処理の相性の良さ
  • Three.jsの簡単さ
  • 作りたいと思ったものを作る喜び(これ大事)
  • 自分のソースのわかりにくさ…精進します…

課題

  • [ ] mousemoveの度にループが呼び出されるので、動かし続けるとだんだんキューブが早くなる(キャンセル処理を入れないと…)
  • [ ] スマホ対応するために、スマホの回転と連動するようにしたい

まとめ

ページを見てもらえればわかるのですが、これは自分のポートレートサイトを作ろうとしてできた副産物(?)です。

ぼく「なんかインパクトのあるものを置かないと覚えてもらえないよな…」
ぼく「3Dオブジェクトとかめっちゃ目立つのでは…?」

という経緯で作ってみました。自分の顔を3Dオブジェクトにして置いておいたら、ポートレートサイトとしてすごいインパクトありますよね。 ~~誰か作って~~

ちなみにポートフォリオサイトはまだ出来上がってません。(わらい)
こんなに長い記事を書いたのは初めてで、読みにくくなってしまったかもしれません。
ここまで読んでいただき、ありがとうございました!
もし改善点や、こうしたらもっと簡単にできる、などありましたら教えてください!!

では!