このエントリーは、KLab Advent Calendar 2015 の12/21の記事です。

 KLab の EM 部とクリエイティブ部テクニカルアーティストグループでテクニカルアーティストをしている(長い…) YuichiSato です。よろしくお願いします。

 普段の業務では Unity 上の C# で 3D グラフィクスゲームを作っている事が多いのですが、Smalltalk 好きとしては「3D グラフィクスプログラムも Smalltalk で書きたいなぁ」と思います。 Smalltalk で 3D グラフィクスプログラミングと言えば、Jun for Smalltalk や最近では Jun4Pharo があります。しかしながら、作ったものを気軽に多くの人に見てもらうには不便です。

 やはり、そういう場合は Web で公開できると便利です。こちらの tetha さんの記事「amber Smalltalk で knockout.js を使ってみた」 を見ると、JavaScript による Smalltalk 処理系の Amber Smalltalk(以下、Amber と略) と WebGL を使えば実現出来そうです。さすがに生の WebGL だとしんどいので、Three.js を Amber で使える様にしてみました。

事前準備

 以下が必要になりますので、インストール等を済ませておきます。使用したバージョンは括弧内の通りです。

  • Node.js(v5.2.0)
  • Bower(1.7.0)
  • Grunt(v0.1.13) <- grunt-cli のバージョン

なお以下では、環境は MacOSX(10.11) で Web ブラウザは Chrome を使いました。こちらのページなどで WebGL が有効になっていることを確認しておきましょう。

Amber の設定と初期化

 まずはこちらに従って Amber の設定を行います。

 次にプロジェクトのディレクトリを作成して Amber の初期化を行います。

% mkdir AmberThreeJS
% cd AmberThreeJS
% amber init

 以下のように入力を求められるので、適当に入力をします。今回はプロジェクト名以外はデフォルトで入力しました。

Please answer the following:
[?] Project title (Application or Library Title) AmberThreeJS
[?] Main class and package of Amber application.
Project name is derived by lowercasing this. (Amberthreejs) 
[?] Description (The Application or The Library doing The Thing.) 
[?] Author name (none)        
[?] Author email (none) 
[?] Namespace of the new Amber package. (amber-amberthreejs) 
[?] Version (0.1.0) 
[?] Project git repository (git://github.com/AccountName/AmberThreeJS.git) 
[?] Project homepage (https://github.com/AccountName/AmberThreeJS) 
[?] Project issues tracker (https://github.com/AccountName/AmberThreeJS/issues) 
[?] Author url (none) 
[?] Licenses (MIT) 
[?] Do you need to make any changes to the above before continuing? (y/N) 

 次の様にしてサーバーを立ち上げ、ブラウザから http://127.0.0.1:4000 にアクセスして動作を確認します。(任意の IP アドレスで立ち上げたい場合はこちらのようにします。)

% amber serve

IDE を選択するダイアログが出てきます。以降では "Helios IDE" を使って説明します。(ポップアップウィンドウを許可する必要があります)

select_ide

アクセス出来ることを確認したら、サーバーは一旦 Ctrl-C で止めておきましょう。

Three.js のインストールと設定

 次に Three.js をインストールします。

% bower install threejs -save

 インストールが出来たら Amber から使えるよう設定を行います。three.js.amd.json というファイルを作成して、以下の様に記述します。今回は ./bower_components/three.js/build/three.js を読みに行きたいので次の様になります。

{
  "paths": {
    "threejs": "build/three"
  }
}

 ファイルの作成が出来たら、次のコマンドを実行します。

% grunt devel

 無事に実行出来たら、config.js, the.js の二つのファイルが作成(または更新)されます。そしたら改めて

% amber serve

でサーバーを立ち上げます。

 ここからは Amber での作業になります。Browser から作成したパッケージを選択して importsthreejs を追加します。

add_threejs_for_imports

追加したら "SaveIt", "Commit package" して、ブラウザでリロードしましょう。これで、Three.js が使えるようになります。ブラウザのデベロッパーツールの JavaScript コンソールから THREE が有効になっていることが確認できます。

console_three

 また、IDE の Workspace からも THREE を "InspectIt" することが出来ます。IDE から Smalltalk のクラスとして確認できると最高なのですが、THREE の Smalltalk のソースコードがあるわけではないので、見ることが出来ません。残念!(最初から入っている jQuery も同様)

サンプルが動くようにしてみる

 まずはこちらの一番下にあるサンプルを動かしてみます。そのまま Amber で動くように書き下すと以下のようになります。

augmentPage
    | scene camera width height renderer geometry material cube render renderBlock |
    scene := THREE Scene new.
    
    width := window innerWidth.
    height := window innerHeight.
    camera := THREE PerspectiveCamera newWithValues: #(75 (width / height) 0.1 1000).

    renderer := THREE WebGLRenderer new.
    renderer setSize: width h: height.

    document body appendChild: renderer domElement.

    geometry := THREE BoxGeometry newValue: 1 value: 1 value: 1.
    material := THREE MeshBasicMaterial newValue: #{ 'color' -> 16r00ff00 }.
    cube := THREE Mesh newValue: geometry value: material.
    scene add: cube.

    camera position z: 5.

    renderBlock := [| x y |
        window requestAnimationFrame: renderBlock.

        cube rotation x: cube rotation x + 0.1.
        cube rotation y: cube rotation y + 0.1.

        renderer render: scene camera: camera.
    ].

    renderBlock value.

これでリロードすると JavaScript としても特にエラーは出ず、canvas 要素が真っ黒に塗りつぶされはしますが、描画されるはずの緑色の立方体が描画されません。そこで原因を調べてみました。オブジェクトを調べるために、コンソールにログ出力をすると次のような違いがあることが分かりました。

 最初の "THREE.WebGLRenderer 73" から次までが正しく描画されない場合、それ以降が正しく描画される場合の出力です。

diff_objects

 正しく描画される場合はオブジェクトのタイプがきちんと出力されていますが、そうでない場合は Object になっているものが多くあります。Object になっていないものといるもので違いをみると、インスタンスを引数ありで生成すると Object になるようです。

 そこで IDE から生成のためのメソッドを調べてみました。 JavaScript のオブジェクトは BlockClosure のインスタンス扱いになっているため、そのメソッドを調べます。生成のためのメソッドは

  • new 引数なし
  • newValue: 引数一つ
  • newValue: value: 引数二つ
  • newValue: value: value: 引数三つ
  • newWithValues: 引数複数

があり、それぞれのメソッドを見てみると new

new
    "Use the receiver as a JS constructor.
    *Do not* use this method to instanciate Smalltalk objects!"
    <return new self()

と素直に生成しています。次の三つは内部で newWithValues: メソッドを使っているので、その実装を見てみると

newWithValues: aCollection
    "Simulates JS new operator by combination of Object.create and .apply"
    <
        var object = Object.create(self.prototype);
        var result = self.apply(object, aCollection);
        return typeof result === "object" ? result : object;
    >

となっていて、これが先ほどの違いになっているようなことが分かりました。そこで、影響がありそうなオブジェクト生成時には new を使うようにしたところ、無事に描画できるようになりました。

rendering

 最終的なコードは次のようになりました。(便宜上、メソッド分割はしていません)

augmentPage
    | scene camera renderer geometry material cube width height renderBlock |

    width := window innerWidth.
    height := window innerHeight.
    
    scene := THREE Scene new.
    camera := THREE PerspectiveCamera new.
    camera fov: 75;
            aspect: width / height;
            near: 0.1;
            far: 1000;
            updateProjectionMatrix.
    
    renderer := THREE WebGLRenderer new.
    renderer setSize: width h: height.
    
    document body appendChild: renderer domElement.

    geometry := THREE BoxGeometry newValue: 1 value: 1 value: 1.
    material := THREE MeshBasicMaterial newValue: #{ 'color' -> 16r00ff00 }.
    cube := THREE Mesh newValue: geometry value: material.
    scene add: cube.
    
    camera position z: 5.
    
    renderBlock := [
        window requestAnimationFrame: renderBlock.
        
        cube rotation x: cube rotation x + 0.1.
        cube rotation y: cube rotation y + 0.1.
        
        renderer render: scene camera: camera.
    ].
    
    renderBlock value.

 ということで、Amber で Three.js を使って 3D グラフィクスプログラミングが出来るようになりました!Smalltalk で書けるので満足感は高いものの、やりやすいかというと正直微妙なところですね…。実をいうと IDE よりもデベロッパーツールとにらめっこしている時間の方が長かったですし。

 とはいえ Amber の理解が深まって良かったです。

Smalltalk への誘い

 この記事を見て Smalltalk に興味を持たれた方はぜひ、本格的な Smalltalk 環境で本来のオブジェクト指向を体験してみてはいかがでしょうか?無料で手に入るメジャーなところでは

  • Squeak <- マルチメディア機能も豊富な楽しい処理系
  • Pharo <- ビジネスっぽく洗練されたクールな処理系

あたりがあります。

明日は、wankoさんです。お楽しみに。