9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

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

Last updated at Posted at 2018-12-16

マウスを追ってくるオブジェクトを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オブジェクトにして置いておいたら、ポートレートサイトとしてすごいインパクトありますよね。 誰か作って

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

では!

9
4
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
4

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?