three.jsで究極小宇宙爆誕

サクッと作ります。

もくじ
 0.three.jsとは
 1.目標
 2.概要
 3.createWorld
 4.addParticles
 5.addSphere
 6.render
 7.おわり


## 0.three.jsとは
WebGLの使い勝手をよくしたjsライブラリです。
 - three.js wiki
http://www56.atwiki.jp/threejs/
そもそものWebGL各ブラウザサポート状況はこちらを参照してください。
 - Can I use...
http://caniuse.com/#feat=webgl


## 1.目標
![img1.png](https://qiita-image-store.s3.amazonaws.com/0/12153/f17070c4-0bbc-385c-9ad2-e79272eda901.png)
http://codepen.io/Xanmia/pen/fDHnc
これをforkして作った

![img2.png](https://qiita-image-store.s3.amazonaws.com/0/12153/ef8dd943-3c04-8200-e1ed-d2e110ea9ea3.png)
http://codepen.io/uriuriuriu/pen/hiJnj
こいつを今日は目指します。


## 2.概要
まずは全体の流れを説明します。
codepenで動くものとソースが見れますので↑のリンクをどうぞ。
下記がざっくりしたものです。

```js:
var _scene, _camera, _light, _renderer, _windowHalfX, _windowHalfY;
var _mouseX = _mouseY = 0;

// init function
var createWorld = function(){
 // three.jsの環境構築を行います。
}

// add element
var addParticles = function(pre_sizeAr){
 // 無限に流れる星を作ります。for文ってほんとすごい。
}
var addSphere = function(){
 // 地球の天地創造メソッドです。
};
var randomColor = function(){
 // ランダムカラーを返す。星の色用です。
}

// render task
var render = function() {
 // 毎フレームのタスクです。マウス座標からカメラ位置の設定&星を動かす。
}

// main
createWorld();
addParticles([1,1,1,2,2,3]);
addSphere();
render();

// event
window.addEventListener('resize', onWindowResize, false);
function onWindowResize(){
 // windowサイズ変更した際の描写範囲調整です。まあ、おまけです。
}

window.addEventListener('mousemove', onWindowMouseMove, false);
function onWindowMouseMove(event){
 // マウスイベントからその座標をグローバル変数に入れます。
}
```

になります。思ったより難しいことはしていません。

では次項から
createWorld();
addParticles([1,1,1,2,2,3]);
addSphere();
render();
の中身について一つづつ説明に入ります。

## 3.createWorld
では早速環境構築しましょう。

```js:createWorld()
// init function
var createWorld = function(){
  //threejs - demo - http://threejs.org/examples/webgl_particles_random.html
  var ele = document.createElement('div');
  document.body.appendChild(ele);
  
  _windowHalfX = window.innerWidth / 2;
  _windowHalfY = window.innerHeight / 2;

  _camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000)
  _camera.position.z = 25000; // first view position, going zoom

  _scene = new THREE.Scene();
  _scene.fog = new THREE.FogExp2(0x600090, 0.0009);  //color of fog - objects are all color

  _light = new THREE.DirectionalLight(0xffffff, 1);
  _scene.add(_light);

  _renderer = new THREE.WebGLRenderer();
  _renderer.setSize(window.innerWidth, window.innerHeight);
  ele.appendChild(_renderer.domElement);

  _renderer.render(_scene, _camera);  // ここで描写
}
```

three.jsはcamera, scene, rendererとlight, mesh, particle等で成り立っています。
sceneにmeshやparticleなど表示したいものやlightを入れて世界を作り、
renderer.render(scene, camera)で描写させます。
ですのでDOMに追加するのはrendererオブジェクトのみです。

これではじめに作るものは完了です。
ここは何つくろうとしてもそんなに変わらないはず！きっと。

```text:tips
・_sceneに設定しているfogは遠くにある物体をその設定した色で塗っていきます。
詳しくは下記ページが参考になりました。
 - 多彩な表現力のWebGLを扱いやすくする「Three.js」 (4/5)
http://www.atmarkit.co.jp/ait/articles/1210/04/news142_4.html

・lightはDirectionalLightを入れて、全部で4種類あります。
```


## 4.addParticles
次は_sceneに入れる無数の星達です。

```js:addParticles([1,1,1,2,2,3])
var addParticles = function(pre_sizeAr){
  var geometry = new THREE.Geometry();
  for (i = 0; i < 5000; i ++){
    var vertex = new THREE.Vector3(Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000);
    geometry.vertices.push(vertex);
  }

  for(var i = 0; i < pre_sizeAr.length; i++){
    var material = new THREE.ParticleBasicMaterial({
      color: randomColor(),
      size: pre_sizeAr[i],
      map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/point.png"),
      blending: THREE.AdditiveBlending,
      transparent: true
    });
    var particles = new THREE.ParticleSystem(geometry, material);
    particles.rotation.set(Math.random() * 6,
                           Math.random() * 6,
                           Math.random() * 6);
    _scene.add(particles); 
  }
}

...

// 使用部分
addParticles([1,1,1,2,2,3]);
```

particles = new THREE.ParticleSystem(geometry, material);で表示させたい星を作るのですが、
geometryは座標、materialは各点のサイズやtextureです。
ここではランダムな座標を5000個入れたgeometryを作り、
引数の数字サイズ&ランダムカラーでmaterialを作り、
引数あるだけparticleを作成しています。

ここでのポイントはgeometryの流用とparticle.rotationのrandom()です。
これがあることによって5000個の星を6つ回転させて置いてるだけなのに、
後述するrenderで簡単で自然に星が動くようになります。
ちまちま全部動かす必要なかったんだ！！！
fork元で死ぬほど参考になった部分でした。みんな頭いいなあ。

```text:tips
・materialで使用しているmapのtextureは白色の中央から透明になっている円です。
var material = new THREE.ParticleBasicMaterial( { size: 4 });
のようにtextureを使わずにも作れますが、四角いドットがガツンとが表示されてしまいます。
```

## 5.addSphere
そうです。そろそろ地球を作ります。

```js:addSphere()
var addSphere = function(){
  var cubeR = 20;
  var geometry = new THREE.SphereGeometry(cubeR, cubeR, cubeR);
  // ■ Google、地球から雲を消す──Googleマップ／Earthが“雲一つない”衛星写真に - ITmedia ニュース
  // http://www.itmedia.co.jp/news/articles/1306/27/news132.html
  var material = new THREE.MeshLambertMaterial({
    map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/earth.jpg"),
    fog:false
  });
  var mesh  = new THREE.Mesh(geometry, material);
  _scene.add(mesh);
};
```

ここはそんなに説明するところが無いはず。
コード読んで貰えれば雰囲気でわかるはず。
なのでさっと説明、
materialでTHREE.MeshLambertMaterial()が出てきました。
particleで使用したTHREE.ParticleBasicMaterial()の他にもmaterialはたくさんあります。
MeshLambertMaterial()はランバート反射表現が可能になるマテリアルです。単語が超かっこいい。
そいつに地球の四角い画像をmappingしているわけです。
これで簡単に地球が出来てしまいました。



## 6.render
最後！毎フレームタスクです。

```js:render()
// render task
var render = function() {
  requestAnimationFrame(render);
  _camera.position.x += (_mouseX - _camera.position.x) * .05;
  _camera.position.y += (- _mouseY - _camera.position.y) * .05;
  var posZ = _camera.position.z + (- _mouseY - _camera.position.z) * .05;
  if(posZ < 80) posZ = 80;
  _camera.position.z = posZ;
  _light.position.set(_camera.position.x,
                      _camera.position.y * 2.5,
                      _camera.position.z);
  _camera.lookAt(new THREE.Vector3(0,0,0));
  for (var i = 0; i < _scene.children.length; i++){
    var object = _scene.children[i];
    if (object instanceof THREE.ParticleSystem){
      object.rotation.y += .0005;
    }
  }
  _renderer.render(_scene, _camera);
}
```

やってることは
・camera座標をマウス座標で動かし、近付き過ぎないようにもし、地球がある(0,0,0)座標を向かせてます。
・lightをcameraとちょっとずらした位置に置いてます。
・for文はparticleでお話したところの続きで、
ここで5000個入ってるparticleをちょっとずつy回転させてます。
ほんのちょっとですがparticle自体が大きいのでこれでも動きすぎかもしれません。


```text:tips
・requestAnimationFrame は setTimeoutと同じような毎フレームアニメーション設定です。
three.js特有のメソッドではありません。違いは下記ページが参考になりました。
 - requestAnimationFrame でフレームと再描画更新を制御する
http://yomotsu.net/blog/2013/01/05/fps.html
```

## 7.おわり
おわり！！
あと、その場で試せるのでcodepen楽しいですよ！！

最後に一応、2013/12/19現在の全js載せておきます。
http://codepen.io/uriuriuriu/pen/hiJnj

```js:
var _scene, _camera, _light, _renderer, _windowHalfX, _windowHalfY;
var _mouseX = _mouseY = 0;


// init function
var createWorld = function(){
  //threejs - demo - http://threejs.org/examples/webgl_particles_random.html
  var ele = document.createElement('div');
  document.body.appendChild(ele);
  
  _windowHalfX = window.innerWidth / 2;
  _windowHalfY = window.innerHeight / 2;

  _camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 3000)
  _camera.position.z = 250; // first view position, going zoom

  _scene = new THREE.Scene();
  _scene.fog = new THREE.FogExp2(0x600090, 0.0009);  //color of fog - objects are all color

  _light = new THREE.DirectionalLight(0xffffff, 1);
  _scene.add(_light);

  _renderer = new THREE.WebGLRenderer();
  _renderer.setSize(window.innerWidth, window.innerHeight);
  ele.appendChild(_renderer.domElement);

  _renderer.render(_scene, _camera);
}




// add element
var addParticles = function(pre_sizeAr){
  var geometry = new THREE.Geometry();
  for (i = 0; i < 5000; i ++){
    var vertex = new THREE.Vector3(Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000,
                                   Math.random() * 2000 - 1000);
    geometry.vertices.push(vertex);
  }

  for(var i = 0; i < pre_sizeAr.length; i++){
    var material = new THREE.ParticleBasicMaterial({
      color: randomColor(),
      size: pre_sizeAr[i],
      map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/point.png"),
      blending: THREE.AdditiveBlending,
      transparent: true
    });
    var particles = new THREE.ParticleSystem(geometry, material);
    particles.rotation.set(Math.random() * 6,
                           Math.random() * 6,
                           Math.random() * 6);
    _scene.add(particles); 
  }
}

var addSphere = function(){
  var cubeR = 20;
  var geometry = new THREE.SphereGeometry(cubeR, cubeR, cubeR);
  // ■ Google、地球から雲を消す──Googleマップ／Earthが“雲一つない”衛星写真に - ITmedia ニュース
  // http://www.itmedia.co.jp/news/articles/1306/27/news132.html
  var material = new THREE.MeshLambertMaterial({
    map: THREE.ImageUtils.loadTexture("https://dl.dropboxusercontent.com/u/90712768/img/earth.jpg"),
    fog:false
  });
  var mesh  = new THREE.Mesh(geometry, material);
  _scene.add(mesh);
};

var randomColor = function(){
	var color = Math.floor(Math.random() * 0xFFFFFF + 0xAAAAAA).toString(16);	//#RRGGBBを取得
  color = ("000000" + color).slice(-6);
	color = "#" + color;
  return color;
}




// render task
var render = function() {
  requestAnimationFrame(render);
  _camera.position.x += (_mouseX - _camera.position.x) * .05;
  _camera.position.y += (- _mouseY - _camera.position.y) * .05;
  var posZ = _camera.position.z + (- _mouseY - _camera.position.z) * .05;
  if(posZ < 80) posZ = 80;
  _camera.position.z = posZ;
  _light.position.set(_camera.position.x,
                      _camera.position.y * 2.5,
                      _camera.position.z);
  _camera.lookAt(new THREE.Vector3(0,0,0));
  for (var i = 0; i < _scene.children.length; i++){
    var object = _scene.children[i];
    if (object instanceof THREE.ParticleSystem){
      object.rotation.y += .0005;
    }
  }
  _renderer.render(_scene, _camera);
}






// main
createWorld();
addParticles([1,1,1,2,2,3]);
addSphere();
render();






// event
window.addEventListener('resize', onWindowResize, false);
function onWindowResize(){
  _windowHalfX = window.innerWidth / 2;
  _windowHalfY = window.innerHeight / 2;
  _camera.aspect = window.innerWidth / window.innerHeight;
  _camera.updateProjectionMatrix();
  _renderer.setSize(window.innerWidth, window.innerHeight);
}

window.addEventListener('mousemove', onWindowMouseMove, false);
function onWindowMouseMove(event){
  _mouseX = event.clientX - _windowHalfX;
  _mouseY = event.clientY - _windowHalfY;
}
```