2014/04/24

three.jsで球を円運動させて太陽系の惑星を描いた。

three.jsの勉強中、単純なオブジェクトを円運動させる方法を知りたくて、それなら調べながら、太陽系の惑星たちの公転運動を描いてみようと思い至った。

球を描く

惑星なのでほぼ球だろう、ということでthree.jsが提供してくれているオブジェクトを使って、太陽と水星〜海王星までを球として表現してみた。3Dのオブジェクトを作るには、Geometry、Material、Meshの3つを使うのが基本だと思っていて、今回各天体を球として表現するのに、

  • THREE.MeshLambertMaterial
  • THREE.SphereGeometry
  • THREE.Mesh
の3つを使った。で、太陽をJavascriptのオブジェクトとして、↓のように書いてみた。
Sun = function() {
  this.material = new THREE.MeshLambertMaterial({ color: 0xff0000 });
  this.geometry = new THREE.SphereGeometry(109, 64, 64);
  this._mesh = new THREE.Mesh(this.geometry, this.material);
}

Sun.prototype = {
  constractor: Sun,
  get mesh() {
    return this._mesh;
  }
}
この太陽を3D空間に配置するには、
var sun = new Sun();
scene.add(sun.mesh);
とすればいい、と。

惑星は、Planetっていうオブジェクトをprototype継承させて個々の惑星を作ってやった。Planetは、

Planet = function(radiusRate, orbitalRadiusRate, color, omegaRate) {
  this.radiusBase = 4;
  this.orbitalRadiusBase = 400;
  this.radiusRate = radiusRate !== undefined ? radiusRate : 1.0
  this.orbitalRadiusRate = orbitalRadiusRate !== undefined ? orbitalRadiusRate : 1.0;
  this.color = color !== undefined ? color : 0x0000ff;
  this._omegaRate = omegaRate !== undefined ? omegaRate : 1;

  this._material = new THREE.MeshPhongMaterial({ color: this.color });
  this._geometry = new THREE.SphereGeometry(this.radiusBase * this.radiusRate, 64, 64);
  this._mesh = new THREE.Mesh(this._geometry, this._material);
  this._mesh.position.x = - this.orbitRadiusBase * this.orbitalRadiusRate;
  this._mesh.position.z = - this.orbitRadiusBase * this.orbitalRadiusRate;

  this._circleGeometry = new THREE.CircleGeometry(this.orbitalRadiusBase * this.orbitalRadiusRate, 2560);
  this._circleGeometry.vertices.shift();
  this._circleMaterial = new THREE.LineBasicMaterial({
    color: 0x111111
    // opacity: 0.1
    // linewidth: 0.01
  });
  this._circle = new THREE.Line(this._circleGeometry, this._circleMaterial);
  this._circle.rotation.x = 90 * Math.PI / 180;
};

Planet.prototype = {
  constractor: Planet,
  get material () {
    return this._material;
  },
  get geometry () {
    return this._geometry;
  },
  get mesh () {
    return this._mesh;
  },
  get circle() {
    return this._circle;
  },
  pivot: function(time) {
    var theta = 1.047 / this._omegaRate * time;
    this._mesh.position.x = Math.cos(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
    this._mesh.position.z = Math.sin(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
  }
};
のようなオブジェクトになった。コンストラクタの引数radiusRateorbitalRadiusRateは、それぞれ地球の半径を1として何倍か、地球の公転軌道半径を1として何倍か、を指定するようにした。それと、omegaRateっていうのは、地球の公転の角速度(rad/s)を1として何倍か、を指定するもので、公転運動の処理で使ってる。さらには、一応公転軌道(=円)を3D空間に表示しようと思って、circleっていう属性を定義した。円を描くのも、例のGeometry,Material,Meshの3点セットが必要だけど、three.jsは円に特化したオブジェクトを定義していて、実際に使ったのは、
  • THREE.CircleGeometry
  • THREE.LineBasicMaterial
  • THREE.Line
の3つ。これもsceneに追加してあげないと表示されないから、例えば、地球は、
Earth = function() {
  Planet.call(this, 1.0, 1.0, 0x0000ff, 1.0);
};
Earth.prototype = Object.create(Planet.prototype);
というオブジェクトを書いて、
var earth = new Earth();
scene.add(earth.mesh);
scene.add(earth.circle);
としてあげた。

円運動

円運動は、単位時間あたり何ラジアン進むか、が重要。で、今回は試しに1年を1分に見立てて色々と計算してみた。つまり、地球は1分かけて太陽の周りを1周する。その場合の地球の角速度は1.047(rad/sec)になった(あってます?)ので、これを使って瞬間瞬間の座標を計算してオブジェクトの位置を変えていけばよさそうだ、と。

で、今回のは、y=0平面上で公転させるってことにすると、xとzの値を時間と角速度と半径から求めることができるなぁ、と思い、結果出来たのが上述のPlanetオブジェクトのpivotというメソッド(↓再掲)。

pivot: function(time) {
    var theta = 1.047 / this._omegaRate * time;
    this._mesh.position.x = Math.cos(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
    this._mesh.position.z = Math.sin(theta) * this.orbitalRadiusBase * this.orbitalRadiusRate;
  }

実際に動かしてみる

実際に動くページをThe Solar System 3Dで公開しました。マウスでグリグリ、コロコロできたり。とりあえず、当初の目的であった、オブジェクトを円運動させる方法はわかったので良かった。