search
LoginSignup
9

More than 1 year has passed since last update.

posted at

updated at

Organization

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

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を使ってカッコいい作品、アプリケーションを開発しましょう!

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
What you can do with signing up
9