マウスを追ってくるオブジェクトを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 )
}
これでとりあえずワイヤーの正方形が描画されました。
ポインタの座標取得と回転角度の処理
つぎに、ポインタの座標に合わせて、オブジェクトの回転角度を変える処理を作成します。
マウスの座標と、描画する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要素上で動くと、オブジェクトがマウスを追従するようになりました。
これでやりたいことはできたのだけど、なにか違う…
もしこのキューブがキャラクターの顔だったら…?
ぼく「遅延が必要なのか!!!」
いい感じに遅延させる
というわけで、キューブの追従に 遅延 を行うように処理を追加します。
遅延処理の考え方
遅延処理のイメージは簡単にこんな感じ
- 現在の角度と、目的の角度に大きく差があれば早く動く
- 目的の角度に近づくほど、ゆっくり動く
そのため、処理としては、 現在の角度と目的の角度の差が回転速度に比例する処理 を作成すればいいことになります。
具体的な計算式はこんな感じ
新しいキューブの角度 = (目的の回転角度 - 現在のキューブの角度) * 遅延定数
この計算をループで呼び出すことで、遅延する角度変更ができるようになります。
ソースに落とし込みます
//マウスに追従して回転角度を設定
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オブジェクトにして置いておいたら、ポートレートサイトとしてすごいインパクトありますよね。 誰か作って
ちなみにポートフォリオサイトはまだ出来上がってません。(わらい)
こんなに長い記事を書いたのは初めてで、読みにくくなってしまったかもしれません。
ここまで読んでいただき、ありがとうございました!
もし改善点や、こうしたらもっと簡単にできる、などありましたら教えてください!!
では!