Rust製クリエイティブコーディング環境 nannou

Rustでもクリエイティブコーディング。グラフィク描画、音の取り扱い、GUIを使ったインタラクティブなアプリ等に対応している。

- [公式サイト](http://nannou.cc/)
- [GitHub](https://github.com/nannou-org/nannou)
- [DOCS.RS](https://docs.rs/nannou/0.8.0/nannou/)

READMEに

> The project was started out of a desire for a creative coding framework inspired by Processing, OpenFrameworks and Cinder, but for Rust.

とある通り、ProcessingやOpenFrameworksやCinderの影響を受けているプロジェクト。

Processingにあるような機能はだいたいある（もしくは実装予定がある）。

原点はデフォルトで画面中央であり、変更する手段があるのかどうかはよくわからない。

[examples/simple_draw.rs](https://github.com/nannou-org/nannou/blob/master/examples/simple_draw.rs)を見ればグラフィックスAPIの雰囲気をつかめる。

[Nannou Examples](https://github.com/nannou-org/nannou#nannou-examples)の手順に従えば[各種サンプル](https://github.com/nannou-org/nannou/tree/master/examples)を実行できる。

ただし、プロジェクト開始から1年経過しておらず、未完成な点や未調整な点が多数ある。一応動く、程度の状態。`tri()`が最速で`rect()`は問題なし、だが`ellipse()`は体感できるほど遅かったりもする。

バイナリが巨大になる(1GB程度)ことやコンパイル時間の長さに対処するため、スケッチをいくつも作って遊ぶような用途ではCargoのワークスペース機能で一括管理するとよいだろうか。

こんな感じに。

```
playground
├── Cargo.lock
├── Cargo.toml
├── sketch_one
│   ├── Cargo.toml
│   └── src
│       └── main.rs
├── sketch_two
│   ├── Cargo.toml
│   └── src
│       └── main.rs
└── target
    ├── ...
```

## ライフゲームを作る例

nannouには**viewモード**と**appモード**がある。

**viewモード**ではメインの関数を一つ定義し、その関数を毎フレーム実行し続ける。

**appモード**では、モデル作成関数とイベントハンドリング関数と描画関数を用意し、イベントハンドリングと描画を毎フレーム行っていく。

上述のsimple_draw.rsでは前者を使っている。この例では後者を使う。

```rust:main.rs
extern crate nannou;
use nannou::prelude::*;

fn main() {
    nannou::run(model, event, view);
}
```

`main`関数で`run`関数を呼び出し、`model`と`event`と`view`を渡す。[modelとeventとviewはそれぞれ特定の引数を受け取る関数である](https://docs.rs/nannou/0.8.0/nannou/fn.run.html)。

```rust:main.rs
#[derive(Copy, Clone, PartialEq)]
enum Cell {
    Live,
    Dead,
}

pub struct Model {
    grid_size: Vector2<f32>,
    cell_size: f32,
    width    : f32,
    height   : f32,
    game     : Vec<Cell>
}
```

`Model`構造体を定義する。この`Model`構造体は、`model`関数内でインスタンスを作成し、`event`関数で内容を更新し、`view`関数から閲覧される。

また、今回はライフゲーム用にCell列挙型も定義している。

続いて`model`関数で準備をする。ここでは最終的に`Model`構造体のインスタンスを返せればよいが、`Model`構造体と関係なくても最初の1回だけ実行したい処理もここに一緒に書いている。

```rust:main.rs
fn model(app: &App) -> Model {
    use self::Cell::*;

    app.set_loop_mode(LoopMode::rate_fps(12.0));

    let grid_size = vec2(9, 9);
    let cell_size = 20.0;
    let width  = (grid_size.x as f32) * cell_size;
    let height = (grid_size.y as f32) * cell_size;

    app.main_window().set_title("Conway's Game of Life");
    app.main_window()
        .set_inner_size_pixels(width as u32, height as u32);


    Model {
        grid_size, cell_size, width, height,
        game: vec![
            Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead,
            Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead,
            Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead,
            Dead, Dead, Dead, Dead, Live, Dead, Dead, Dead, Dead,
            Dead, Dead, Dead, Dead, Dead, Live, Dead, Dead, Dead,
            Dead, Dead, Dead, Live, Live, Live, Dead, Dead, Dead,
            Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead,
            Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead,
            Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead, Dead,
        ],
    }
}
```

`event`を定義する。ライフゲームのルールに則り次世代を作成し、modelに再代入して返している。

```rust:main.rs
fn event(_app: &App, mut model: Model, event: Event) -> Model {
    use self::Cell::*;

    match event {
        Event::Update(_update) => {
            let x_len = model.grid_size.x;
            let y_len = model.grid_size.y;
            let next_generation = model.game.iter().enumerate()
                .map(|(i, current_cell)| {
                    let middle = i / x_len;
                    let center = i % x_len;
                    let top    = ((middle+y_len - 1) % y_len) * x_len;
                    let bottom = ((middle + 1) % y_len) * x_len;
                    let left   = (center+x_len - 1) % x_len;
                    let right  = (center + 1) % x_len;

                    let middle = middle * x_len;
                    let around = [
                            top + left,
                            top + center,
                            top + right,
                            middle + left,
                            middle + right,
                            bottom + left,
                            bottom + center,
                            bottom + right,
                        ]
                        .iter()
                        .filter(|around_cell_i| model.game[**around_cell_i] == Live)
                        .count();
                    match around {
                        3 => Live,
                        2 => *current_cell,
                        _ => Dead
                    }
                })
                .collect();
            model.game = next_generation;
        }
        _ => ()
    }

    model
}
```

最後に`view`関数。

```rust:main.rs
fn view(app: &App, model: &Model, frame: Frame) -> Frame {
    let draw = app.draw();
    draw.background().color(BLACK);

    model.game.iter().enumerate()
    .for_each(|(i, cell)| {
        let color = match cell {
            Cell::Live => WHITE,
            Cell::Dead => return,
        };

        let xi = i % model.grid_size.x;
        let yi = i / model.grid_size.x;
        let x = model.cell_size/2.0 - model.width/2.0
                + (xi as f32) * model.cell_size;
        let y = -model.cell_size/2.0 + model.height/2.0
                - (yi as f32) * model.cell_size;

        draw.rect()
            .x_y(x, y)
            .w_h(model.cell_size, model.cell_size)
            .color(color);
    });

    draw.to_frame(app, &frame).unwrap();
    frame
}
```

これで

    $ cargo run

すれば、白いグライダーが右下に向かって進んでいくはずだ。

![黒背景に白一色のグライダー](https://qiita-image-store.s3.amazonaws.com/0/242566/d66bd3a5-54bc-02fe-86d6-3908fd64fe23.gif)