Think Stitch
PRINCIPIA  最近の更新


遷移の曲線:スプライン曲線と楕円の話

世にある状態遷移図を描くことのできるツールを見回してみると、遷移を表すのに線分をつなぎ合わせた折れ線を使っているものが多いようです。 なぜなのか理由はわかりません。伝統なのか、UML の仕様書がそうだからなのか、あるいは某社のツールがそうなのか・・・。 多分に趣味の問題とは思いますが、SyncStitch ではなめらかな曲線を選びました。

状態遷移図を描く一番の目的は、プロセスの振る舞いを視覚的に表すことです。 コミュニケーションのためにも使うことがありますから、美しく描ければそれに越したことはありません。 しかし、プレゼンテーション資料を作るときなどもそうですが、絵を描き始めるとついつい細部が気になってしまって、気がつくと体裁を直すためだけに何時間も使っていたりすることがあります(私だけでしょうか?)。 SyncStitch は、状態遷移図をささっと描いて、ささっと検査できるところがいいところです。 そのためには絵が描きやすくて、振る舞いを考えることに集中できる方がよいわけです。

曲線を選んだ理由の1つはそこにあります。 折れ線だと、水平線や垂直線がちょっと傾いていたり、斜め線の角度がそろっていなかったりすると気になって仕方がないのです。 その点、曲線ならば多少ゆがんでいても気になりません。もともと曲がってますから。 (余計に気になるという人がいたら・・・ごめんなさい。) 「長さや角度をそろえる操作を用意すればいいじゃないか」とおっしゃるかもしれませんが、それこそ罠です。 そんなものを用意したら、体裁を整えることに燃えてしまうじゃありませんか。 (でも大きさ、位置等をそろえる操作の要求は来てます・・・実は私自身もちょっと負けそうです。いずれつけちゃうかもしれません。)

スプライン曲線

曲線と決めたら、次はどのような曲線にするかという選択の問題があります。 これも世のお絵かきツールを見ると、ベジェ曲線(ベジェスプライン, Bézier spline)を使っているものが多いようです。 しかしベジェ曲線はおおざっぱに曲線を描くにはあまり向いていないと思うのです。 1つは制御点の操作が面倒であること、もう1つは制御点が曲線上にないからです。

そこで SyncStitch ではスプライン曲線を使うことにしました。 これならば制御点が曲線上にあるのでわかりやすいし、少ない制御点で欲しい曲がり方をする曲線を描くことができます。

はじめは2次のスプラインを試してみました。 しかし曲線の曲がり具合が気に入らなかったので、3次にすることにしました。

N + 1 個の点 (x0, y0), (x1, y1), ..., (xN, yN) があるとき(N >= 2)、スプライン曲線はパラメータ t を使って次の 2N 個の方程式で表すことができます。 ak, bk, ck, dk, ek, fk は定数係数です。 つまり、N 個のちび曲線を連結して1つの長い曲線を作っているわけです。

N + 1 個の点の座標からこれら 6N 個の係数を求めれば、曲線を描くことができます。 係数を決めるために、次の条件を設定することにしました。

これを数式にすると次のようになります(ちょっとごまかし)。

これで合計 6N 個の条件がありますから、係数を求めることができます。

ベジェスプライン曲線

次は描画なんですけど、スプライン曲線の描画をしてくれるグラフィックスライブラリは稀です。 一方、ベジェ曲線を描いてくれるものはたくさんあります。 そこで求めたスプライン曲線をベジェ曲線に変換して描画してもらうことにしました。

ご存知の人が多いと思いますが、3次のベジェ曲線は次のように定義された曲線です。 始点、終点と、2つの制御点があります。 まずそれぞれをつなぐ線分を比 t : 1-t で内分する点を求め、さらにそれらを接続する線分を同じ比で内分し、最後の内分点を求めます。 パラメータ t を 0 から 1 の間で動かすとき、最後の内分点が描く曲線がベジェ曲線です。

数式で書くと次のようになります。 見てのとおりの3次式なので、係数を置き換えればスプラインと同じというわけです。

これで描画することができるようになりました。

曲線の移動

話は変わって、曲線の操作の話です。 状態図形を移動するとき、つながっている遷移の曲線も全体がついてくるようにしました。 制御点(ハンドル)1つ1つを移動しなければならないような事態は避けたかったからです。

相手先の接続点も移動するようにしました。 状態図形を左に持っていったのに、接続点だけ右に残されていたのではかっこわるいからです。 さらに、接続点はできるだけ状態図形に垂直に入射するようにしました。 これを実現するにはかなり苦労しました。

もちろん必要であれば、ハンドルを個別に動かすこともできます。

ループ:遷移元と遷移先が同じ場合

遷移元状態と遷移先状態が同じ遷移のことを、ユーザガイドではループと呼びました。 これも世のツールでは折れ線で描いている場合が多いようですけど、SyncStitch では見た目重視で円弧にしました。

ループは状態図形に接続したまま、周上を移動させることができます。 ちょっと大きさが変わることがあるのがかっこ悪いんですけど。

これもなかなか難しくて、苦労しました。 どうやっているかというと2つの接続点の間の、状態図形の周長を計算しているのです。 知っている人は「あれっ?」と思ったかもしれません。 なぜかというと、状態図形のコーナーは楕円の一部だからです。 楕円の周長は楕円関数になってしまう(ってなんかへんな言い方ですけど)わけですけど、ふつうのライブラリには入ってませんよね。 数値計算なんてほとんど知らないので、教科書を開いてルンゲ・クッタ法で数値積分しました(名前が好き)。 級数で求めた方がよかったのかもしれません。

2013/09/16
© 2013,2014,2015 PRINCIPIA Limited