Rust のゲームエンジン Piston で矩形波を描く

## 動機
正弦波の組み合わせで矩形波（くけいは）ができることを4歳の娘に視覚的に理解させようと思い、下に示すアニメーションを作ってみました。Rust で。

N=1
![n1.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353186/2620c5c3-b433-bbac-04bb-60b5b285bd11.gif)
N=3
![n3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353186/46ea7873-5e2b-ea58-d6e8-67a17249fa46.gif)
N=10
![n10.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/353186/17f75cec-66bb-e7cb-6684-3a70b6dde68c.gif)
眺めてるだけでも楽しいですね。娘のウケはイマイチでした。

## Piston
Piston は Rust 製のゲームエンジンです。Piston は初めて使うので、[Rustでスネークゲームを作る【pistonチュートリアル】](https://qiita.com/Hiroya_W/items/7ab479062215faa00964) の記事がとても参考になりました。

ウィンドウの表示には`piston_window`crate を使います。

```rust:main.rs
use piston_window::*;
const FPS: u64 = 60;

fn main() {
    let mut window: PistonWindow = WindowSettings::new("Hello, Wave!", [1000, 480])
        .exit_on_esc(true)
        .samples(4)
        .build()
        .unwrap();
    window.set_ups(FPS);

    while let Some(e) = window.next() {
        if let Some(_) = e.render_args() {
            // 描画処理
        }
        if let Some(_) = e.update_args() {
            // 状態の更新処理
        }
    }
}
```

## データ構造
正弦波を組み合わせるということは、円盤の端っこに円盤が乗っていて、その円盤の端っこにまた円盤が乗っていて、それぞれがぐるぐると回っているということです。上のアニメーションの青い○が円盤です。なのでそれぞれの円盤は親となる円盤を持っていて、ある円盤自身の中心位置は親円盤の状態に依存していることになります。Rust でこのようなデータ構造を表してみましょう。

### 失敗例
まずは円盤構造体の定義。

```rust
struct Disk {
    center: (f64, f64), // 中心位置
    radius: f64,        // 半径
    degree: f64,        // 角度
}
```

続いて`App`構造体を定義します。メンバとして`Vec<Disk>`を持たせます。その`Vec`に円盤を詰め込んでいって、自身の1個前の円盤が親円盤という状態です。`update`メソッドで状態の更新を行います。しかしこれはコンパイルエラー。なぜだか分かりますよね。

```rust
struct App {
    disks: Vec<Disk>,
}

impl App {
    fn update(&mut self) {
        for (i, d) in self.disks.iter_mut().enumerate() {
            // 自身の円盤角度を進める
            d.degree += 1.0;
            // 2個目以降の円盤なら
            if i > 0 {
                // 1個前の円盤（親円盤）の参照を得て...
                let parent = self.disks.get(i - 1).unwrap();
                // 自身の中心位置を更新
                d.center = (
                    parent.center.0 + parent.radius * (parent.degree / 180.0 * PI).cos(),
                    parent.center.1 + parent.radius * (parent.degree / 180.0 * PI).sin()                    
                );
            }
        }
    }
}

let disks = vec![];
for _ in 0..N {
    let disk = // 省略
    disks.push(disk);
}
let mut app = App { disks };
app.update();
```
### 成功例
Rust でこういうツリー構造を作るときは`Rc`と`RefCell`を使うのが一般的（？）みたいです。では円盤構造体を書き直します。（`Rc`は[参照カウント方式のスマートポインタ](https://doc.rust-jp.rs/book/second-edition/ch15-04-rc.html)、`RefCell`は["実行時に"借用規則を強制する型](https://doc.rust-jp.rs/book/second-edition/ch15-05-interior-mutability.html)なんですって。）親円盤は無い場合もあるので`Option`で包んでおきます。

```rust
use std::rc::Rc;
use std::cell::RefCell;

struct Disk {
    parent: Option<Rc<RefCell<Disk>>>, // 親円盤への参照
    center: (f64, f64), // 中心位置
    radius: f64,        // 半径
    degree: f64,        // 角度
}
```
`App`構造体も書き直します。`Vec`には`Disk`構造体ではなく、`Disk`へのポインタを持たせます。`update`メソッドも以下のようになります。これで親円盤の状態によって自身の中心位置を更新できるようになりました。

```rust
struct App {
    // disks: Vec<Disk>,
    disks: Vec<Rc<RefCell<Disk>>>,
}

impl App {
    fn update(&mut self) {
        for d in self.disks.iter() {
            // 自身への可変の参照を得る
            let mut disk = d.borrow_mut();
            // 自身の円盤角度を進める
            disk.degree += 1.0;
            // 親円盤があるなら
            if let Some(parent) = disk.parent.clone() {
                // 親円盤への不変の参照を得る
                let parent = parent.borrow();
                // 自身の中心位置を更新
                disk.center = (
                    parent.center.0 + parent.radius * (parent.degree / 180.0 * PI).cos(),
                    parent.center.1 + parent.radius * (parent.degree / 180.0 * PI).sin(),
                );
            }
        }
    }
}

let disks = vec![];
// 最初の円盤には親円盤はない
let mut parent = None;
for _ in 0..N {
    let disk = Disk {
        parent,
        // 省略
    };
    // 円盤へのポインタを作る
    let disk_rc = Rc::new(RefCell::new(disk));
    // ポインタを複製して Vec に追加
    disks.push(disk_rc.clone());
    // 親円盤へのポインタ
    parent = Some(disk_rc);
}
let mut app = App { disks };
app.update();
```
## 描画
`App`構造体に`render`メソッドを書きます。実際にはもう少し工夫が必要になりますが簡単のために省略します。是非ご自身で実装を考えてみてください。

```rust
impl App {
    fn update(&mut self) {
        // 省略
    }

    fn render<E: GenericEvent>(&self, e: &E, window: &mut PistonWindow) {
        window.draw_2d(e, |c, g, _| {
            clear(color::WHITE, g);
            self.disks.iter().for_each(|d| {
                let disk = d.borrow();
                // 省略
                // piston の line 関数とか circle_arc 関数とか使って
                // ゴリゴリと描画処理を書いていく
            });
        });
    }
}
```
`main`関数はこんな感じになります。

```rust
fn main() {
    let mut disks = vec![];
    for _ 0..N {
        // disks の初期化処理は省略
        // 矩形波になるように Disk の半径は (2k-1) ずつ小さくして
        // 周波数は (2k-1) ずつ大きくしないといけません
    }
    let mut app = App { disks };

    let mut window: PistonWindow = WindowSettings::new("Hello, Wave!", [1000, 480])
        .exit_on_esc(true)
        .samples(4)
        .build()
        .unwrap();
    window.set_ups(FPS);

    while let Some(e) = window.next() {
        if let Some(_) = e.render_args() {
            // 描画処理
            app.render(&e, &mut window);
        }
        if let Some(_) = e.update_args() {
            // 状態の更新処理
            app.update();
        }
    }
}
```

## まとめ
- Piston を使ってみたよ
- `Rc`、`RefCell` の使いどころが分かったよ
- 動くものができるとたのしい:blush: