LoginSignup
17
9

More than 3 years have passed since last update.

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

Last updated at Posted at 2020-03-10

Nannou

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

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

とりあえず円を出力してみる

$ cargo new hello_nannou
$ cd hello_nannou
Cargo.toml
[package]
name = "hello_nannou"
version = "0.1.0"
edition = "2018"

[dependencies]
nannou = "0.13.1"
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

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

解説

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

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

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

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

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

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

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

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

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

app.draw()

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

draw.ellipse()
    .color(STELLBLUE);

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

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

で描画します。

簡単なアニメーションを実装する

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

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を変化させています。

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

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

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

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

ex2.gif

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

Eventを扱う

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

pub enum Event {
    WindowEvent {
        id: window::Id,
        simple: Option<WindowEvent>,
    },
    DeviceEvent(winit::event::DeviceId, winit::event::DeviceEvent),
    Update(Update),
    Suspended,
    Resumed,
}

キーが押された、マウスが移動したなどのイベントはWindowEventでパターンマッチをすることで取得できます。

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

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

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

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

struct Model {
    speed: f32
}

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

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

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を参照するようにしました。この時点でビルドは変わらず通り、特にアニメーションは変化が無いはずです。

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

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

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

ソースコード全体

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も利用可能です。

公式ドキュメントやGithubレポジトリのexamplesが非常に充実しているので、実装もサクサク進めやすいです。ぜひNannouを使ってカッコいい作品、アプリケーションを開発しましょう!

17
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
9