総コン Advent Calendar 2019の15日目の記事です。
はじめに
はじめまして。これが初投稿になります。
大学で信号解析やアニメーション技術などを習っていますが、本当に数学ってたくさん出てきますよね… サイン、コサイン、いつ使うん?とか思っていた頃が私にはありました。(皆さんもあったかも…?)
本題に入りますが、私は先日イーズイン・イーズアウト処理、というものを習いました。それを実装してこい!っていう課題が出されたので、私なりに考えてみました。そこで思いついたのが、ベクトルを使ってやろうということでした。
ベクトルとは
ベクトル - Wikipedia
ベクトルとは、大きさだけでなく、向きももった量の事をいいます。簡単に言えば、「どれくらいの長さの矢印がどこを向いているか」がベクトルです。詳しいことは高校の教科書が一番丁寧に書いてあるので、それ読んでください。
ベクトルにはいろんな用語がありましたよね。以下にいくつかまとめます。
単位ベクトル
長さが $1$ のベクトル。ベクトルの偏角を $\theta$ とすれば $\left( \cos{\theta} , \sin{\theta} \right)^T$と表せます。この $T$ は転置行列という意味。三角関数、早速出てきましたね。以下もベクトルはこの書き方をしていきます、ご了承ください。行列を習っていない人は、「なんかわかんないけど $T$ が書いてある」とだけ思ってくれればいいです。
位置ベクトル
要は座標。ある点の座標は、原点からその点までのベクトルっていう解釈もできるよねっていう話。
方向ベクトル
ベクトルの向きが知りたい。今は大きさなんて何だっていい。 $\left( 1,2 \right)^T$ も $\left( 2,4 \right)^T$ も向きは一緒だよね。
イーズイン・イーズアウト
まとめて「イージング」と呼びます。加速度が変化していくモーションです。
Processingで実装しています。後でソースコードの解説します。
主にCGアニメーションで物を動かす際に使われます。
速度がいきなり最高になるものって無いですよね。例えば、車が動き出した瞬間に40km/sの速さになることはありません。(実際そうなったら首がもげそう…)なので、だんだん速くなってだんだん遅くなる動きが必要になってくるのです。
ソースコード解説
動作環境はProcessing 3.5.3
です。
float easing;
PVector v, from, to, own;
void setup() {
size(800, 600);
easing = 0.05;
own = new PVector(50, 50);
from = new PVector(50, 50);
to = new PVector(50, 50);
v = new PVector();
}
void draw() {
background(0);
if (from.dist(to)/2 > from.dist(own)) {
//easeIn
v = PVector.fromAngle(new PVector(to.x-from.x, to.y-from.y).heading());
v = v.mult(PVector.dist(from, own) * easing);
} else if (round(from.dist(to) - from.dist(own)) > 0) {
//easeOut
v = PVector.fromAngle(new PVector(to.x-from.x, to.y-from.y).heading());
v = v.mult(PVector.dist(own, to) * easing);
} else {
v.setMag(0);
}
own.add(v);
fill(255);
circle(own.x, own.y, 50);
}
void mousePressed() {
if (v.mag() == 0) {
from = to;
to = new PVector(mouseX, mouseY);
from.sub(PVector.fromAngle(new PVector(to.x-from.x, to.y-from.y).heading()));
}
}
各メソッドの説明はPVector 公式リファレンスを見てください。(英語です)
イーズイン・イーズアウトのどちらも同じ計算
v = PVector.fromAngle(new PVector(to.x-from.x, to.y-from.y).heading());
をしています。これは始点から終点への向きを計算し、その向きの単位ベクトルを算出しています。これにスカラー値を掛けることによって、一定の向きに対して速さを変化させることが簡単にできます。逆向きなら負のスカラー値を掛ければいいのです。
オブジェクトが終点にある程度近づくとv.setMag(0)
でベクトルの大きさを0にしています。まさしく、速度が0ということです。
速さは、次の2点間距離の定数倍から求めています。
- イーズイン:始点と現在地点
- イーズアウト:現在地点と終点
目的地の座標を更新したあと、
from.sub(PVector.fromAngle(new PVector(to.x-from.x, to.y-from.y).heading()));
という処理をやってます。これは、始点を長さ1だけ終点とは逆の向きに離しています。こうしないとイーズインが始まりません。始点と現在地点が重なってるから速度が生まれないのです。
ベクトルを用いたわけ
一般化が可能だから
この考え方を利用すれば、理論上、何次元でも対応させることができます。特に3次元に対応させるのが簡単になるのは大きいことだと思います。
(from.z - to.z
などxやyと同じようにz軸の計算を加えるだけ)
わかりやすいから
各座標の計算を分けると読みにくくないですか?
「なんか似たようなことずっと言ってんな」
「xとyを入れ替えただけじゃん、長くてわかりにくい」
とか思われそう…
おわりに
ベクトルが扱えると、座標計算が楽になりますよね?上記の話で「そんなの大学で学んでないからわからない」というものは無かったんじゃないでしょうか。
高校数学でも立派なプログラムが作れるんですね。儚い…
ここまでお読みいただき、ありがとうございました。