開発コストを削減「ラボ開発」って?
開発コストを削減「ラボ開発」って?
2017.05.25
#59
それいけ!フロントエンド

three.jsで、太陽系をつくってみよう!〜ちょっと応用編〜

はっちゃん

こんにちは、はっちゃんです。
最近筋トレにハマっています。

前回までの記事では、three.jsを使って宇宙と地球を作りました。


今回は、上記の記事で紹介した技術を応用し、「太陽系」を作ってみたいと思います。

テクスチャの読み込み

まずは星の数分の展開図を用意して、TextureLoaderで読み込ませる必要があります。

展開図は下記からダウンロードできます。

ですが、TextureLoaderのloadメソッドは、ひとつずつしか画像を読み込めません。

1

前回作った地球は、地表・雲・宇宙空間でそれぞれ別のテクスチャを使用しています。なので、loadメソッドを3回記述する必要がありました

しかし、今回は使用する画像が多いため、画像をまとめて読み込んだあとに物体を生成させます。

テクスチャの読み込みに使用するツール「Preload.js」

使用するのは、CreateJSのライブラリモジュールのひとつ、「PreloadJS」です。

PreloadJSは、画像・音声・JSONなどの読み込み処理ができ、読み込み率を、イベントで監視して取得することができます。
その読み込み率を演出用のJavaScriptに渡してあげることで、読み込み状況に合わせた演出を実現できます。

使用方法

CDNで用意したJSを読み込みます。

その後JS側でキューを作成し、loadQueue.on(‘complete’)内でロードしたい内容を記述します。最後にloadQueue.loadManifestを実行すれば、読み込み開始です。

 

▼HTML

<script src="https://code.createjs.com/preloadjs-0.6.2.min.js"></script>

 

▼Javascript

// 読み込むテクスチャーリスト
let manifest = [
  { id: 'sun', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/sun.jpg'}, // 水星
  { id: 'mercury', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/mercury.jpg'}, // 水星
  { id: 'venus', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/venus.jpg'}, // 金星
  { id: 'earth', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/earth.png'}, // 地球
  { id: 'crowd', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/crowd.png'}, // 雲
  { id: 'mars', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/mars.jpg'}, // 火星
  { id: 'jupiter', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/jupiter.jpg'}, // 木星
  { id: 'saturn', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/saturn.jpg'}, // 土星
  { id: 'saturn-ring', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/saturn-ring.jpg'}, // 土星の輪
  { id: 'ouranos', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/ouranos.jpg'}, // 天王星
  { id: 'ouranos-ring', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/ouranos-ring.jpg'}, // 天王星の輪
  { id: 'pluto', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/pluto.jpg'}, // 冥王星
  { id: 'neptune', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/neptune.jpg'}, // 海王星
  { id: 'universe', src: 'https://raw.githubusercontent.com/82mou/sandbox/master/img/universe.jpg'} // 宇宙空間
];

// ロードキューを作成
let loadQueue = new createjs.LoadQueue();

// ロード完了を監視
loadQueue.on('complete', function() {
  // loadQueueからロードした画像データを取得
  let sunImg = loadQueue.getResult('sun');
  let mercuryImg = loadQueue.getResult('mercury');
  let venusImg = loadQueue.getResult('venus');
  let earthImg = loadQueue.getResult('earth');
  let crowdImg = loadQueue.getResult('crowd');
  let marsImg = loadQueue.getResult('mars');
  let jupiterImg = loadQueue.getResult('jupiter');
  let saturnImg = loadQueue.getResult('saturn');
  let saturnRingImg = loadQueue.getResult('saturn-ring');
  let ouranosImg = loadQueue.getResult('ouranos');
  let ouranosRingImg = loadQueue.getResult('ouranos-ring');
  let plutoImg = loadQueue.getResult('pluto');
  let neptuneImg = loadQueue.getResult('neptune');
  let universeImg = loadQueue.getResult('universe');

  // three.jsで使えるテクスチャーに変換
  let textureSun = new THREE.Texture(sunImg);
  let textureMercury = new THREE.Texture(mercuryImg);
  let textureVenus = new THREE.Texture(venusImg);
  let textureEarth = new THREE.Texture(earthImg);
  let textureCrowd = new THREE.Texture(crowdImg);
  let textureMars = new THREE.Texture(marsImg);
  let textureJupiter = new THREE.Texture(jupiterImg);
  let textureSaturn = new THREE.Texture(saturnImg);
  let textureSaturnRing = new THREE.Texture(saturnRingImg);
  let textureOuranos = new THREE.Texture(ouranosImg);
  let textureOuranosRing = new THREE.Texture(ouranosRingImg);
  let texturePluto = new THREE.Texture(plutoImg);
  let textureNeptune = new THREE.Texture(neptuneImg);
  let textureUniverse = new THREE.Texture(universeImg);

  // 【重要】更新を許可
  textureSun.needsUpdate = true;
  textureMercury.needsUpdate = true;
  textureVenus.needsUpdate = true;
  textureEarth.needsUpdate = true;
  textureCrowd.needsUpdate = true;
  textureMars.needsUpdate = true;
  textureJupiter.needsUpdate = true;
  textureSaturn.needsUpdate = true;
  textureSaturnRing.needsUpdate = true;
  textureOuranos.needsUpdate = true;
  textureOuranosRing.needsUpdate = true;
  texturePluto.needsUpdate = true;
  textureNeptune.needsUpdate = true;
  textureUniverse.needsUpdate = true;
});

// テクスチャーのロードを開始
loadQueue.loadManifest(manifest);

※こちらの記事を参考にさせていただきました。ありがとうございます!
https://ics.media/entry/5239
http://qiita.com/sawa-zen/items/cba55a23411753f1353e

惑星の工房となる関数をつくる

太陽系のオブジェクトを作っていきます。

宇宙空間、及び、太陽系すべては球体でできているので、ひとつの関数に引数を渡してやるだけで作れます。

今回、下記のように引数を命名しました。

  • texture・・・textureデータ
  • radius・・・半径
  • widthSegments・・・経度分割数。多いほど経線が細かくなる。
  • heightSegments・・・緯度分割数。緯線が細かくなる。
  • planetName・・・一部同じ関数を使い回せない惑星があるので、if文で判定するための文字列。
sun = planetFactory(textureSun, 50, 20, 20, 'isSun');
mercury = planetFactory(textureMercury, 5, 20, 20);
venus = planetFactory(textureVenus,10 , 20, 20);
earth = planetFactory(textureEarth,13 , 20, 20, 'isEarth');
mars = planetFactory(textureMars,7 , 20, 20);
jupiter = planetFactory(textureJupiter, 30, 20, 20);
saturn = planetFactory(textureSaturn, 18, 20, 20);
ouranos = planetFactory(textureOuranos,20 , 20, 20);
pluto = planetFactory(texturePluto,18 , 20, 20);
neptune = planetFactory(textureNeptune, 17, 20, 20);
universe = planetFactory(textureUniverse, 10000, 20, 20, 'isUniverse');

function planetFactory (texture, radius, widthSegments, heightSegments, planetName) {
  let sphere,
      sphereEarth;
  
   if(planetName === 'isSun') {
    sphere = new THREE.Mesh(
      new THREE.SphereGeometry(radius, widthSegments, heightSegments), // 形状     
      new THREE.MeshBasicMaterial({ // 材質     
        map: texture,
        side: THREE.DoubleSide // 裏からも見えるようにする
      })
    );
    sphere.position.set(0, 0, 0);
  } else if(planetName === 'isEarth') {      
    sphere = new THREE.Group();

    sphereEarth = new THREE.Mesh(
      new THREE.SphereGeometry(13 , 20, 20), // 形状
      new THREE.MeshLambertMaterial({ // 材質     
        map: texture                           
      })
    );

    crowd = new THREE.Mesh(
      new THREE.SphereGeometry(14, 20, 20), // 形状     
      new THREE.MeshLambertMaterial({ // 材質     
        map: textureCrowd,
        transparent: true,
        side: THREE.DoubleSide // 裏からも見えるようにする                         
      })
    );

    sphere.add(sphereEarth);
    sphere.add(crowd);

    sphere.position.set(
      Math.random() * 500 - 250,
      Math.random() * 500 - 250,
      Math.random() * 500 - 250
    );
  } else if(planetName === 'isUniverse') {
    sphere = new THREE.Mesh(
      new THREE.SphereGeometry(radius, widthSegments, heightSegments), // 形状     
      new THREE.MeshLambertMaterial({ // 材質     
        map: texture,
        side: THREE.DoubleSide // 裏からも見えるようにする
      })
    );
    sphere.position.set(0, 0, 0);
  } else {
    sphere = new THREE.Mesh(
      new THREE.SphereGeometry(radius, widthSegments, heightSegments), // 形状
      new THREE.MeshLambertMaterial({ // 材質     
        map: texture                           
      })
    );
    sphere.position.set(
      Math.random() * 500 - 250,
      Math.random() * 500 - 250,
      Math.random() * 500 - 250
    );
  }
  scene.add(sphere);
  return sphere;
}

長いので、もう少し細かくコードを解説していきます。

オブジェクトをグルーピングする

地球に関してですが、地表と雲に分けてオブジェクトを作っています。これらはひとつのオブジェクトにまとめてしまいましょう。

まとめるには、THREE.Groupクラスを使用します。

sphere = new THREE.Group();

sphereEarth = new THREE.Mesh(
  new THREE.SphereGeometry(13 , 20, 20), // 形状
  new THREE.MeshLambertMaterial({ // 材質     
    map: texture                           
  })
);

crowd = new THREE.Mesh(
  new THREE.SphereGeometry(14, 20, 20), // 形状     
  new THREE.MeshLambertMaterial({ // 材質     
    map: textureCrowd,
    transparent: true,
    side: THREE.DoubleSide // 裏からも見えるようにする                         
  })
);

sphere.add(sphereEarth);
sphere.add(crowd);

ランダムにおいてみる

正式な位置は難しいので、ランダムにおいてそれっぽくしてみましょう。
太陽と宇宙空間以外をランダムに配置します。なお、太陽はMeshBasicMaterialを使用して光の影響を受けないようにし、光源と同じ位置に配置しています。

sphere.position.set(
  Math.random() * 500 - 250,
  Math.random() * 500 - 250,
  Math.random() * 500 - 250
);

完成

完成したコードがこちらです。
大きめに作っているので、codepen本家サイトで見るのをおすすめいたします。

See the Pen March Blog Solar System by k_hatsushi (@hatsushi_kazuya) on CodePen.

まとめ

テクスチャーを変えて球体を複製するだけで、だいぶ太陽系っぽいものが作れちゃいました!

ここまで理解すれば、あとは工夫次第でさまざまな表現が可能になると思います。是非試してみて下さい!