Rust でクリエイティブ・コーディング - Nannou -


Nannou とは

  • Nannou は Rust のクリエイティブ・コーディング用ツールキット
  • クリエイティブ・コーディングとは、プログラミングによりビジュアルアートやアニメーションなどのインタラクティブ創作を行う活動
  • 名前は Aphex Twin のオルゴールをサンプリングした曲 Nannou より


sketch

nannou::sketch() を使うと最も簡単に結果を得ることができる。

背景色を青に描画するだけの例は以下。

use nannou::prelude::*;

fn main() {
    nannou::sketch(view).run();
}

fn view(app: &App, frame: Frame) {
    let draw = app.draw();
    draw.background().color(BLUE);
    draw.to_frame(app, &frame).unwrap();
}

app.draw()draw::Draw を取得し、これに描画して Frame に渡す。


polyline

冒頭のアニメーションは、以下のコードによる。

use nannou::prelude::*;

fn main() {
    nannou::sketch(view).run()
}

fn view(app: &App, frame: Frame) {
    let draw = app.draw();
    draw.background().color(BLACK);

    let win = app.window_rect();
    let t = app.time;

    // Decide on a number of points and a weight.
    let n_points = 10;
    let weight = 8.0;
    let hz = ((app.mouse.x + win.right()) / win.w()).powi(4) * 1000.0;
    let vertices = (0..n_points)
        // A sine wave mapped to the range of the window.
        .map(|i| {
            let x = map_range(i, 0, n_points - 1, win.left(), win.right());
            let fract = i as f32 / n_points as f32;
            let amp = (t + fract * hz * TAU).sin();
            let y = map_range(amp, -1.0, 1.0, win.bottom() * 0.75, win.top() * 0.75);
            pt2(x, y)
        })
        .enumerate()
        // Colour each vertex uniquely based on its index.
        .map(|(i, p)| {
            let fract = i as f32 / n_points as f32;
            let r = (t + fract) % 1.0;
            let g = (t + 1.0 - fract) % 1.0;
            let b = (t + 0.5 + fract) % 1.0;
            let rgba = srgba(r, g, b, 1.0);
            (p, rgba)
        });

    // Draw the polyline as a stroked path.
    draw.polyline()
        .weight(weight)
        .join_round()
        .points_colored(vertices);

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

draw.polyline() で計算した頂点を描画している。


App

App はよりきめ細やかな制御が必要な場合に利用する。

以下のような構成となる。

use nannou::prelude::*;

fn main() {
    nannou::app(model)          // Start building the app and specify our `model`
        .event(event)           // Specify that we want to handle app events with `event`
        .update(update)         // rather than `.event(event)`, now we only subscribe to updates
        .simple_window(view)    // Request a simple window to which we'll draw with `view`
        .run();
}

// the Model is where we define the state of our application.
struct Model {}

// The model function is run once at the beginning of the nannou app and produces a fresh,
// new instance of the Model that we declared previously, AKA the app state.
fn model(_app: &App) -> Model {
    Model {}
}

// The event function is some code that will run every time some kind of app event occurs.
fn event(_app: &App, _model: &mut Model, _event: Event) {
}
fn update(_app: &App, _model: &mut Model, _update: Update) {
}

// The view allows us to present the state of the model to a window by drawing to its Frame
// and returning the frame at the end.
fn view(_app: &App, _model: &Model, frame: Frame) {

    frame.clear(WHITE);

    // get canvas to draw on
    let draw = _app.draw();

    draw.text("Hello, World!").font_size(20).color(BLACK);
    draw.to_frame(_app, &frame).unwrap();
}

model() では、モデルを初期化する。アプリケーション開始時に一度だけ実行される。

event() は、マウス操作やキーボード操作などのイベント発生時に実行される。update() は一定時間経過の都度実行される(デフォルトで60fps)。

view() では、モデルの状態をウィンドウに描画する(ウインドウの中心が原点の座標系となっている)。

例えば以下の例では、ウインドウの中心に100x100の四角形を描画する。

draw.rect()
    .x_y(0.0, 0.0)
    .w_h(100.0, 100.0)
    .color(PLUM);

先のコードでは以下のようなウインドウが表示される。