Nannouで図形を描画して動かしてみる

## [Nannou](https://nannou.cc/)

> Nannou is an open-source, creative-coding toolkit for Rust.

電子アートやデザイン等々を作成するためのライブラリです。Processingの影響を受けているみたいです。
基本的な図形の描画はもちろん、音声の出力やUIの描画もサポートするなど、機能は多岐に渡ります。

## とりあえず円を出力してみる
```shell
$ cargo new hello_nannou
$ cd hello_nannou
```

```toml:Cargo.toml
[package]
name = "hello_nannou"
version = "0.1.0"
edition = "2018"

[dependencies]
nannou = "0.13.1"
```

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

struct Model {}

fn main() {
    nannou::app(model)
        .event(event)
        .simple_window(view)
        .run();
}

fn model(_app: &App) -> Model {
    Model {}
}

fn event(_app: &App, _model: &mut Model, _event: Event) {
}

fn view(app: &App, _model: &Model, frame: Frame) {
    let draw = app.draw();

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

これを `cargo run` すると...

![ex1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314367/d9b59e44-e5c4-28ca-d93a-968445751553.png)

中央に円が描画されます。

### 解説
```rust
fn main() {
    nannou::app(model)
        .event(event)
        .simple_window(view)
        .run();
}
```

main関数に注目すると、`nannou::app` で得られたインスタンスに `event` `simple_window` `run` の順でメソッドを実行しています。それぞれの引数に `model` `event` `view` という関数を渡しています。

```rust
fn model(_app: &App) -> Model {
    Model {}
}
```

`model()`では、アプリケーションの状態を保持しておくためのModelを初期化しています。先のサンプルでは特に状態持つ必要がなかったため、空のstructのインスタンスを返しています。

```rust
fn event(_app: &App, _model: &mut Model, _event: Event) {
}
```

`event()`では、キーボードやマウスなどのイベントを受け取ったときに呼ばれる関数です。この関数内でイベントをハンドリングして、Modelを更新することで後述するViewに対して変化を与えます。

```rust
fn view(app: &App, _model: &Model, frame: Frame) {
    let draw = app.draw();

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

そして最後に `view()`です。

```rust
app.draw()
```

で一枚のキャンパスを模したインスタンスが取得出来ます。

```rust
draw.ellipse()
    .color(STELLBLUE);
```

`ellipse()`でキャンパスの中央に対して円を定義し、

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

で描画します。

## 簡単なアニメーションを実装する
状態によって変化を起こさない簡単なアニメーションであれば、`view()`だけで実装が可能です。

```rust
fn view(app: &App, _model: &Model, frame: Frame) {
    let t = app.time;
    let draw = app.draw();

    let x = 4.0 * t as f32; 
    let point = pt2(x.cos(), x.sin()) * 100.0;

    draw.ellipse()
        .color(STEELBLUE)
        .w(200.0)
        .h(200.0)
        .x_y(point.x, point.y);
    draw.to_frame(app, &frame).unwrap();
}
```

`app.time`でアプリケーションが起動してから経過した時間(f32)が取得できるので、これをタイマーとしてviewを変化させています。

```rust
let x = 4.0 * t as f32; 
let point = pt2(x.cos(), x.sin()) * 100.0;
```

取得したタイマーをもとに、`sin()`, `cos()` を使って二次元の点を導出して、

```rust
draw.ellipse()
    .color(STEELBLUE)
    .w(200.0)
    .h(200.0)
    .x_y(point.x, point.y);
```

得られた点で円の位置を決定します。これを実行することで、

![ex2.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314367/352615dd-c4c0-4e06-b12b-429b1d58d2ab.gif)

円がくるくる移動します。

### Eventを扱う
`event(app: &App, model: &mut Model, event: Event)`はアプリケーションに対するイベントが発生するたびに呼び出されます。`enum Event`は以下のような定義になっており、

```rust
pub enum Event {
    WindowEvent {
        id: window::Id,
        simple: Option<WindowEvent>,
    },
    DeviceEvent(winit::event::DeviceId, winit::event::DeviceEvent),
    Update(Update),
    Suspended,
    Resumed,
}
```
キーが押された、マウスが移動したなどのイベントはWindowEventでパターンマッチをすることで取得できます。

先程定義した関数`event`に`Up`もしくは`Down`キーが押されたことを検出する機構を実装してみます。

```rust
fn event(_app: &App, _model: &mut Model, event: Event) {
    match event {
        Event::WindowEvent{ id: _id, simple: window_event } => {
            if let Some(KeyPressed(key)) = window_event {
                match key {
                    Key::Up => println!("Pressed Up!!!"),
                    Key::Down => println!("Pressed Down!!!"),
                    _ => {},
                }
            }
        }
        _ => {},
    }
}
```

![ex3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314367/98081f23-a2bc-4f92-0212-d07258281c37.gif)

### Modelを使ってアニメーションを変化させる

Modelに回転するスピードをもたせてみましょう。

```rust
struct Model {
    speed: f32
}

fn model(_app: &App) -> Model {
    Model {
        speed: 4.0
    }
}
```

Modelにspeedというフィールドを新たに定義し、`model()`で初期化するように変更しました。更に、

```rust
fn view(app: &App, model: &Model, frame: Frame) {

    // 省略

    let x =  model.speed * t as f32; 
    let point = pt2(x.cos(), x.sin()) * 100.0;

    // 省略
}
```

`view()`で`Model`に定義した`speed`を参照するようにしました。この時点でビルドは変わらず通り、特にアニメーションは変化が無いはずです。

ここで先程追加したキー入力を出力する機構に少し手を加えます。

```rust
fn event(_app: &App, model: &mut Model, event: Event) {
    match event {
        Event::WindowEvent{ id: _id, simple: window_event } => {
            if let Some(KeyPressed(key)) = window_event {
                match key {
                    Key::Up => { model.speed = model.speed + 1.0 },
                    Key::Down => { model.speed = model.speed - 1.0 },
                    _ => {},
                }
            }
        }
        _ => {},
    }
}
```

Upキーを押したときにspeedが1.0加算され、Downキーを押したときにspeedが1.0減算されるように変更しました。
この状態でビルド、実行すると...

![ex4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/314367/1eb54bde-e666-6f47-14cd-5b2e7d459896.gif)

Up, Downキーを押すたびに円が回転する速さが変わります。キーを押した時点での円の位置を考慮していないため、キーを押すたびに円の位置がズレますがそこはご愛嬌。

### ソースコード全体
```rust:main.rs
use nannou::prelude::*;

struct Model {
    speed: f32
}

fn main() {
    nannou::app(model)
        .event(event)
        .simple_window(view)
        .run();
}

fn model(_app: &App) -> Model {
    Model {
        speed: 5.0
    }
}

fn event(_app: &App, model: &mut Model, event: Event) {
    match event {
        Event::WindowEvent{ id: _id, simple: window_event } => {
            if let Some(KeyPressed(key)) = window_event {
                match key {
                    Key::Up => { model.speed = model.speed + 1.0 },
                    Key::Down => { model.speed = model.speed - 1.0 },
                    _ => {},
                }
            }
        }
        _ => {},
    }
}

fn view(app: &App, model: &Model, frame: Frame) {
    let t = app.time;
    let draw = app.draw();

    draw.background().color(BLACK);

    let x =  model.speed * t as f32; 
    let point = pt2(x.cos(), x.sin()) * 100.0;

    draw.ellipse()
        .color(STEELBLUE)
        .w(200.0)
        .h(200.0)
        .x_y(point.x, point.y);
    draw.to_frame(app, &frame).unwrap();
}
```

## まとめ
描画方法、アニメーション、キーイベントの扱いなどを一通り実装してみました。

nannouでは線、多角形、円など基本的な図形はもちろん、スライダーやXYパッドなどのUIも利用可能です。

[公式ドキュメント](https://guide.nannou.cc/welcome.html)やGithubレポジトリの[examples](https://github.com/nannou-org/nannou/tree/master/examples)が非常に充実しているので、実装もサクサク進めやすいです。ぜひNannouを使ってカッコいい作品、アプリケーションを開発しましょう！