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で公開しました。マウスでグリグリ、コロコロできたり。とりあえず、当初の目的であった、オブジェクトを円運動させる方法はわかったので良かった。

2014/03/30

node(express.js)でcsrf対策する。

node(express.js)でcsrfに対する対策を施す方法について書いてみる。といっても、express.jsはcsrf対策のためのミドルウェアを提供しているので、その使い方の話。(ここで使っているexpress.jsのバージョンは3.4.8です。)

express.jsのcsrfミドルウェア

express.jsが提供するミドルウェアに、csrf っていうのがあってコレを使うと簡単にcsrf対策ができる。

使い方 - app.js

expressコマンドでアプリケーションを作ると、app.jsというファイルが出来て、この中にアプリケーションやサーバの設定を書くんだけど、その中で次のコードを書く。

// use session support.
app.use(express.session());

// use csrf middleware.
app.use(express.csrf());
app.use(function(req, res, next) {
  res.locals._csrf = req.csrfToken();
  next();
});
前述のこのページにあるとおり、csrfミドルウェアを使うには、session()よりも下に書けってあるので、注意が必要。

使い方 - view

csrfミドルウェアが生成してくれるトークンをformに埋める必要があるので、viewでformを書くときには、

form(action='/some_action', method='form')
  input(type='hidden', name='_csrf', value='#{_csrf}')
  :
のように、hiddenに_csrfのトークンを埋めておく。(これはテンプレートエンジンにjadeを使った場合の例)

これで、postなリクエストにcsrfトークンが含まれていなかったり、不正なトークンが含まれていると、expressが検出してエラーを上げてくれるようになる。

2014/03/23

twitter-bootstrapを使う時のベーシックなlayout.jade

twitter-bootstrapをexpressjsなアプリに適用するにあたって、jadeのレイアウト(layout.jade)にbootstrapのcssとjavascriptを埋めます。ベーシックなレイアウトをgistに上げました。

node(expressjs)なアプリをデプロイする、capistrano3で簡単に。

node(expressjs)なアプリを作って、capistrano2でデプロイしていたのだけど、capistrano3が登場してずいぶんスマートになったとのことで、capistrano3に載せ替えてみた、という話です。

capistranoって何?

失礼なほどざっくり言ってしまえば、capistranoはrubyで書かれたデプロイツールで、gitやsvnなどのリポジトリからモジュールを取得、デプロイ先のしかるべきディレクトリに配置し、dbの変更、assetsの生成、プロセスの再起動なんかを自動でやってくれるすぐれものです。 capistrano2っていうのが長く広く使われていたところに、capistrano3が登場していろいろとスマートになったようです。

capistrano + node

nodeなアプリを対象とするにあたり、リモート(デプロイ先)でのnodeのパスを正しく設定したり、npmでライブラリ群をインストールしたり、っていうところをどうやるのか、というのが肝になるのかなぁ、と思っていたら、

っていうのが既にあったので、使わせて頂きました。

capistrano-nvm

capistrano上で動作するnvmタスクを提供してくれる。 ローカルなnvmにも、システムワイドなnvmにも対応している。僕のサーバはnvmをシステムワイドで入れているので助かった。([node] nvmをシステムワイドに適用する。)

capistrano-npm

capistrano上で動作するnpmタスクを提供してくれる。 Railsアプリのデプロイプロセスでbundle installを実行するように、npm installを走らせる。 もともとcapistrano-npmだけ追加してやったらnpmのパス(、つまりnode関連のパス)が解決できずどうしたものか、とはまっていたところに、capistrano-nvmと併せて使ったらば解決できたので、セットで使うのがいいのかと。

deploy

実際にデプロイするには、

  1. リモートホスト(デプロイ先ホスト)の環境設定
  2. capistranoのインストール
  3. capistrano-nvmのインストール
  4. capistrano-npmのインストール
  5. Capfile, deploy.rbの作成
  6. deploy
という流れになりますね。

リモートホスト(デプロイ先ホスト)の環境設定

リモートで作業するユーザとSSHの設定が基本になります。 ユーザ作って、ディレクトリ掘って、SSHでフォワードできれば良さそう。 capistranoの本家サイトにある、Authentication & Authorisationのページのとおりにやりました。

capistrano,capistrano-nvm,capistrano-npmのインストール

gemコマンドでも、bundle経由でもいいと思います。今回はgemコマンド叩いてインストールしてしまいました。

$ gem install capistrano
$ gem install capistrano-nvm
$ gem install capistrano-npm

Capfile, deploy.rbの作成

ここがcapistrano2から変わったところの1つで、2の頃はcapifyっていうコマンドがあって、一番最初にcapifyしていたのが、 cap installに変わりました。 cap install すると、Capfileとdeploy.rbとかstaging.rbとかproduction.rbが作られます。環境別の設定ファイルのひな形が生成されます。いわゆるマルチステージに対応しています。stagingっていうのは、順productionというのか、本番の手前っていう位置づけの環境と考えるといいと思います。

Capfileではそのプロジェクトにおけるcapistranoの環境を書きます。 deploy.rbとdeploy/*.rbには、デプロイに関する詳細やタスクを書きます。 自作でタスクを作る場合は、lib/capistrano/tasksに拡張子.capを作ります。

今回は、カスタムタスクはなしで、Capfile、deploy.rb、production.rbだけ弄って、それぞれ次のように書きました。

# Capfile
# Load DSL and Setup Up Stages
require 'capistrano/setup'

# Includes default deployment tasks
require 'capistrano/deploy'

# Includes tasks from other gems included in your Gemfile
#
# For documentation on these, see for example:
#
#   https://github.com/capistrano/rvm
#   https://github.com/capistrano/rbenv
#   https://github.com/capistrano/chruby
#   https://github.com/capistrano/bundler
#   https://github.com/capistrano/rails
#
# require 'capistrano/rvm'
# require 'capistrano/rbenv'
# require 'capistrano/chruby'
# require 'capistrano/bundler'
# require 'capistrano/rails/assets'
# require 'capistrano/rails/migrations'
require 'capistrano/nvm' # <= 追加
require 'capistrano/npm' # <= 追加

# Loads custom tasks from `lib/capistrano/tasks' if you have any defined.
Dir.glob('lib/capistrano/tasks/*.cap').each { |r| import r }
# config/deploy.rb
# config valid only for Capistrano 3.1
lock '3.1.0'

set :application, 'application_name'
set :repo_url,    'ssh://hostname.your.repo/path/to/your_repo.git'

# Default branch is :master
# ask :branch, proc { `git rev-parse --abbrev-ref HEAD`.chomp }

# Default deploy_to directory is /var/www/my_app
# set :deploy_to, '/var/www/my_app'
set :deploy_to, '/var/www/application_name'

# Default value for :scm is :git
# set :scm, :git

# Default value for :format is :pretty
# set :format, :pretty

# Default value for :log_level is :debug
# set :log_level, :debug

# Default value for :pty is false
# set :pty, true

# Default value for :linked_files is []
# set :linked_files, %w{config/database.yml}

# Default value for linked_dirs is []
# set :linked_dirs, %w{bin log tmp/pids tmp/cache tmp/sockets vendor/bundle public/system}
set :linked_dirs, %w{log node_modules}

# Default value for default_env is {}
# set :default_env, { path: "/opt/ruby/bin:$PATH" }

# Default value for keep_releases is 5
# set :keep_releases, 5

set :nvm_type, :system
set :nvm_node, 'v0.10.26'
set :nvm_map_bins, %w{node npm}

namespace :deploy do
  desc 'Restart application'
  task :restart do
    on roles(:app), in: :sequence, wait: 5 do
      # Your restart mechanism here, for example:
      # execute :touch, release_path.join('tmp/restart.txt')
      execute :sudo, :restart, 'name_of_upstart_script'
    end
  end

  after :publishing, :restart

  after :restart, :clear_cache do
    on roles(:web), in: :groups, limit: 3, wait: 10 do
      # Here we can do anything such as:
      # within release_path do
      #   execute :rake, 'cache:clear'
      # end
    end
  end

end

deploy

ここまで準備ができたところで、デプロイします。 capistrano2の頃のように、cap deploy:setupとかはもう要らない。いきなり、 cap deployでよい。

$ cap production deploy

2014/03/14

node(express.js)でbootstrap3を使う、npmで簡単に。

express.jsを使ったnodeなWebアプリを作っていて、twitter-bootstrap使いたいな、と思った。かつて、bootstrap2の頃、本家からダウンロードして、cssとjavascriptをpublicに置いてできたー、とか思ったこともあったけど、npmとかないのかな?って思って探したらあった、じゃあ使ってみよう、という話です。

less-middlewareとtwitter-bootstrap-3.0.0

今回使ってみたのは、

の2つです。 less-middlewareは、express.jsがデフォルトで使用するメタCSS。twitter-bootstrap-3.0.0は、自分でpackage.jsonに書いてインストールしましたよ。
{
  "name": "appname",
  "version": "0.0.1",
  "private": true,
  "scripts": {
    "start": "node app.js"
  },
  "dependencies": {
    "express": "3.4.8",
    "jade": "*",
    "less-middleware": "*",
    "twitter-bootstrap-3.0.0": "*",
    :
  }
}

app.jsでless-middlewareの設定を書く。

twitter-bootstrap-3.0.0がインストールできたらば、app.jsに手を入れます。
いろいろとmiddlewareの登録をしている部分で、 var bootstrapPath = path.join(__dirname, 'node_modules', 'twitter-bootstrap-3.0.0');

app.use(require('less-middleware')({
  src:    path.join(__dirname, 'assets', 'less'),
  paths:  [path.join(bootstrapPath, 'less')],
  dest:   path.join(__dirname, 'public', 'stylesheets'),
  prefix: '/stylesheets'
}));
のようにless-middlewareを登録します。この記述は、
  • lessのソースは、assets/less配下に置く。
  • bootstrapPathの下のlessもlessのコンパイル対象に含める。
  • コンパイルして出来たcssファイルは、public/stylesheets配下に置く。
  • prefixは、lessのページに"Path which should be stripped from the public pathname."とあるけど、イマイチよくわかっていません。
という具合になります。
これで、bootstrapのlessはnpmに任せられるようになります。bootstrapをカスタマイズするには、assets/lessの下の自前lessファイルに書いていけば、カスタマイズできる、と。

2014-03-16追記

less-middlewareのバージョンが上がると、上述のオプションの指定の仕方が変わるそうで。 Migration 0.1.x 0.2.0 · emberfeather/less.js-middleware Wikiに詳しい説明がありますね。0.2.1-betaっていうのが今の時点で使えて、上の指定をするとエラーとなるので、次のように直したら、うまくいった。

var less = require('less-middleware');
var bootstrapPath = path.join(__dirname, 'node_modules', 'twitter-bootstrap-3.0.0');

app.use(less(path.join(__dirname, 'assets', 'less'),
             { dest: path.join(__dirname, 'public'),
               preprocess: {
                 path: function(pathname, req) {
                   return pathname.replace('/stylesheets', '');
                 }
               }
             },
             { paths:  [path.join(bootstrapPath, 'less')] }
            ));

bootstrapのJavascriptはどうすんのさ?

これを楽する方法がまだわかっていませんorz。
node_modules/twitter-bootsrap-3.0.0の中にあるjsファイルをpublic/javascripts配下にコピーして使っているけど、これは今後の課題です。