rust nannouでハートをいっぱい描いちゃおう

nannou

ハートをいっぱい描きたい!

と思って(え?ちょっと気持ち悪い?)ハートをいっぱい描くプログラムをrust nannouで書きました。

今回は構造体やimplを使って、ハート描画のための座標の計算や保持などを行うようなアルゴリズムにしました。

ハートは、三角関数を使って、座標計算をしています。

ハートをいっぱい描くプログラム

では、さっそくrust nannouで書いたハートをいっぱい描くプログラムを見ていきましょう。

ハートの座標算出する関数

まずは、ハートを描くためのハートの座標を算出する関数を見ていきます。

ハートを描く方法は色々ありますが、今回は三角関数を使った有名な数式を使いました。

$$
\begin{eqnarray}
&x&=16\sin^{3}(t)\\
&y&=13\cos(t)-5\cos(2t)-2\cos(3t)-\cos(4t)
\end{eqnarray}
$$

この数式を使って、ハートの座標を計算する関数を作ります。

関数に必要な引数は、媒介変数tとハートの大きさを変化させるための値(ここではsとします)の2つです。

返値は、座標Vector2を返します。

fn heartxy(t: f32, s: f32) -> Vector2 {
     let x = 16.0 * my_sin(1.0, t).powi(3);
     let y = my_cos(13.0, t) - my_cos(5.0, 2.0 * t) - my_cos(2.0, 3.0 * t) -      my_cos(1.0, 4.0 * t);

vec2(s*x,s*y)
}

こんな感じです。

補足:三角関数のカスタマイズ

ここで出てきているmy_sinとmy_cosは、rustの三角関数を自分用にカスタマイズするで解説しています。

自分用に使いやすいようにカスタマイズしています。

例えば\(2\sin(3t)\)をrustで書くと

(3.0*t).sin()*2.0

みたいな感じに書くことになって、ちょっと一瞬見ずらいですよね。それを補うために自分用にカスタマイズした関数を作って使ってます。

三角関数の他の言語との違いは衝撃的過ぎたrustの三角関数で解説していますので、気になる方は是非見てみてください。

というわけで、ハートを計算する関数が完成しました。

ハートの構造体とメソッド

次にハートを作るための構造体とメソッドを作っていきましょう。

ハートの構造体は、実際に描画するための座標を収納するものです。

ひとまずソースを見ましょう。

struct Heart {
     hpoint: Vec,
 }
impl Heart {
     pub fn new(p: Vector2, s: f32) -> Self {
         let mut hpoint = Vec::new();
         let mut t = 0.0;
         while t < 2.0 * PI {
             hpoint.push(heartxy(t, s) + p);
             t += 0.1
         }
         Heart { hpoint }
     }
 }

hpointっていうのが、実際に描画するための座標データです。これが、ハートの本体です。

メソッドはシンプルにnewだけです。

ハートをインスタンス化するときに「どこに(p)」「どれぐらいの大きさで(s)」描くのかを指定できるようにしています。

newする時にそれを引数として与えて座標を計算してインスタンス化しています。

ハートをいっぱい描画する

ハートを計算する関数、ハートを生成する構造体とメソッドができたので、最後に描画をしていきましょう。

今回は、一定周期で、ハートをポンポンと描画して、そして、だんだんと消えていくっていうプログラムを作りました。

Modelの初期化とupdate

struct Model {
     heart: Heart,
 }
 fn model(app: &App) -> Model {
...
...
    let heart = Heart::new(vec2(0.0, 0.0), 10.0);
    Model { heart }
 }

初期化では、原点にサイズ10のハートを作ります。

updateで、位置やサイズを変化させてハートを生成していきます。

fn update(app: &App, model: &mut Model, _update: Update) {
     let win = app.window_rect();
     let t = app.elapsed_frames();
     if t % 9 == 0 {
         model.heart = Heart::new(
             vec2(
                 random_range(win.left(), win.right()),
                 random_range(win.top(), win.bottom()),
             ),
             10.0 * random::(),
         );
     }
 }

random_rangeを使って、ウィンドウの左右と上下いっぱいいっぱいの間で、位置をランダムに生成します。サイズもランダムに作ります。

普通にupdateを使ってハートを描画すると60fpsぐらいでハートが出てくるので、目がチカチカして死んでしましますので

app.elapsed_frames()でフレーム数を取得して、それに応じて、ハートを生成するようにします。

ここでは、9フレームごとにハートを作る感じです。

描画関係

view関数の中も見ていきましょう。

ひとまずソース

fn view(app: &App, model: &Model, frame: Frame) {
...
...
...
     let win = app.window_rect();
     draw.rect().wh(win.wh()).rgba(0.0, 0.0, 0.0, 0.006);

     draw.polyline()
         .weight(10.0)
         .points(model.heart.hpoint.clone())
         .color(DEEPPINK);
     draw.path()
         .fill()
         .points(model.heart.hpoint.clone())
         .color(HOTPINK);
     draw.to_frame(app, &frame).unwrap();
 }

draw.rect()あたりの2行は、残像を創り出すための処理です。

これは、rust nannouで残像を描く処理でやり方を紹介しています。気になる方は確認してください。

draw.polyline()とdraw.path()でハートを描いています。

polylineで輪郭を描いて、path()で塗りつぶしをしています。

これでいっぱいハートです

まとめ

今回は、ハートをいっぱい描くをテーマに構造体やメソッドやラッパー(関数のカスタマイズ)をやりました。

ランダムに表示させるだけじゃなくて、例えばふわふわ浮き上がっていくなんていうのも構造体を使って定義していれば、簡単に実装できると思います。

今度は、泡がふわふわ上がっていくのを作ってみようかな?

ではまた!

コメント

タイトルとURLをコピーしました