HTML5のCanvasでつくるダイナミックな表現―CreateJSを使う

第10回 ドラッグの軌跡を滑らかな曲線で描く

この記事を読むのに必要な時間:およそ 7 分

前回は,BoxBlurFilterによりぼかしたイメージのインスタンスに,AlphaMaskFilterでマスクしたもとのイメージのインスタンスを重ね,マウスのドラッグでアルファマスクを描いた(EaselJSALPHAMASK FILTER参照)。参考までに,jsdo.itのサンプルコードを改めて掲げる。この表現そのものは基本的に変えない。今回は,連続する座標を滑らかな軌跡で描く手法について考えたい。

軌跡の描き方をお題と比べる

まず,前掲jsdo.itのコードのもととなった前回の第9回コード2がどのようにマウスポインタの座標の軌跡を描いたか,確かめておこう。ステージ上でマウスボタンを押すStage.stagemousedownイベントでドラッグが始まり(startWipe()),ドラッグしている間のアルファマスクのアニメーションはTicker.tickイベントのリスナー(wipe())が行う。

ドラッグを始めるリスナー関数(startWipe())は,マウスポインタの座標をPointオブジェクトの変数(oldPoint)に納め,描画するGraphicsオブジェクト(wipingShape)の準備を整える。アニメーションのリスナー関数(wipe())は,変数(oldPoint)の座標と今現在のマウスポインタの座標(mouseXとmouseY)を,Graphics.lineTo()メソッドにより直線で結ぶ。そして,新たなマウスポインタの座標で,変数(oldPoint)の値を書き替える。

var wipingShape;

var oldPoint = new createjs.Point();

function startWipe(eventObject) {
  var mousePoint = getMousePoint();
  oldPoint.x = mousePoint.x;
  oldPoint.y = mousePoint.y;

  wipingShape.graphics
  .setStrokeStyle(radius * 2, "round", "round");
}

function wipe(eventObject) {

  var mousePoint = getMousePoint();
  var mouseX = mousePoint.x;
  var mouseY = mousePoint.y;
  wipingShape.graphics
  .beginStroke(createjs.Graphics.getRGB(0x0, 0.15))
  .moveTo(oldPoint.x, oldPoint.y)
  .lineTo(mouseX, mouseY);
  oldPoint.x = mouseX;
  oldPoint.y = mouseY;

}

お題のALPHAMASK FILTERも,ドラッグを始めるリスナー関数(handleMouseDown())とアニメーションのリスナー関数(handleMouseMove())で軌跡を描いている※1)。マウスポインタの座標を変数(oldPt)にとって用いているのは第9回コード2と同じだ。しかし,大きく異なることがふたつある。第1に,Graphics.curveTo()メソッドで曲線を描いていることだ。第2に,もうひとつ座標を変数(oldMidPt)に納めている。これは,今現在のマウスポインタの座標と古い座標の平均,つまり中点を計算している。「>> 1」「/ 2」とほぼ同じと捉えてよい※2)。

細かな中身はこれから順を追って説明する。だから,ふたつのコードの間に違いがふたつあるということだけ,取りあえず頭に留めてほしい。なお,このコードには実は重大な問題がある。それは,本稿の最後に明かしたい。

var drawingCanvas;
var oldPt;
var oldMidPt;

function handleMouseDown(event) {
  oldPt = new createjs.Point(stage.mouseX, stage.mouseY);
  oldMidPt = oldPt;

}

function handleMouseMove(event) {

  var midPoint = new createjs.Point(oldPt.x + stage.mouseX >> 1, oldPt.y+stage.mouseY >> 1);
  drawingCanvas.graphics.setStrokeStyle(40, "round", "round")
  .beginStroke("rgba(0,0,0,0.15)")
  .moveTo(midPoint.x, midPoint.y)
  .curveTo(oldPt.x, oldPt.y, oldMidPt.x, oldMidPt.y);
  oldPt.x = stage.mouseX;
  oldPt.y = stage.mouseY;
  oldMidPt.x = midPoint.x;
  oldMidPt.y = midPoint.y;

}
※1
「ALPHAMASK FILTER」は,アニメーションの関数(handleMouseMove())Stage.stagemousemoveイベントのリスナーとして加えている。アニメーションの表現としては,Ticker.tickイベントで扱った場合と大きな違いはない。
※2
「>> 1」は,2進数の整数としてひと桁下げる演算だ。したがって,2で割って小数値を切捨てるのに等しい。

2次ベジエ曲線を描く

Graphics.curveTo()メソッドは,内部的にはGraphics.quadraticCurveTo()メソッドを参照する。ActionScriptと同じ名前のメソッドを設けることで,ユーザーになじみやすくした。「2次ベジエ」(Quadratic Bezier)の曲線を描くメソッドだ。以降はGraphics.quadraticCurveTo()メソッドで代表する。2次ベジエは,始点と終点のふたつの座標に加えて,ひとつのコントロールポイントで曲線を定める図1左図)。Adobe Illustratorなどのベクターグラフィックスを描くアプリケーションでおなじみのベジエ曲線は,コントロールポイントがふたつある「3次ベジエ」(Cubic Bezier)図1右図)。

図1 2次ベジエと3次ベジエの曲線のつくり

図1 2次ベジエと3次ベジエの曲線のつくり

ActionScript 3.0コンポーネントリファレンスガイド「Graphics」curveTo()メソッドの項より引用。

2次ベジエは,3次ベシエよりコントロールポイントがひとつ少ない分,プログラムの扱いは楽だ。2次ベジエのコントロールポイントは,両端の座標から曲線の接線を延ばし,その交点に置く図2)。Graphics.quadraticCurveTo()メソッドの引数には,コントロールポイントのxy座標と曲線で結ぶ先のxy座標を渡す。

Graphicsオブジェクト.quadraticCurveTo(コントロールx座標, コントロールy座標, 終点x座標, 終点y座標)

図2 コントロールポイントと両端を結ぶ直線は曲線の接線になる

図2 コントロールポイントと両端を結ぶ直線は曲線の接線になる

jsdo.itシミュレーションサンプル

曲線の設定や引き始めの座標決めなどは,Graphics.lineTo()メソッドで直線を描く場合と基本的に同じだ。Graphics.quadraticCurveTo()メソッドの文法や曲線の描き方について,詳しくはEaselJSのGraphicクラスで2次ベジエ曲線を描くをお読みいただきたい。

Graphicsオブジェクト.beginStroke(カラー)
.setStrokeStyle(各スタイル, …)
.moveTo(始点x座標, 始点y座標)
.quadraticCurveTo(コントロールx座標, コントロールy座標, 終点x座標, 終点y座標);

さて,2次ベジエ曲線を描くには,始点と終点(これらを合わせて「アンカーポイント」と呼ぶことにする)のほかに,コントロールポイントを与えなければならない。すると,連続した座標をひとつおきでアンカーポイントとコントロールポイントに振分けることが思いつく。けれど,ジグザグの軌跡については,滑らかな曲線にはならない図3)。

図3 ひとつおきにコントロールポイントを定めたのではジグザグの座標の軌跡には角が生じる

図3 ひとつおきにコントロールポイントを定めたのではジグザグの座標の軌跡には角が生じる

そこで,ひとつ工夫を加える。連続した座標は,すべてコントロールポイントにしてしまう。そして,座標の中点をアンカーポイントとして結ぶのだ図4)。こうすれば,軌跡は滑らかな曲線になる※3)。ただし,軌跡の曲線が座標の上を通過しないことには注意しておこう。それでも,座標の変化が緩やかなら軌跡はそれらの近くをとおり,変化が激しくても滑らかな曲線を描く。

図4 座標はコントロールポイントとして中点をアンカーポイントに定める

図4 座標はコントロールポイントとして中点をアンカーポイントに定める

jsdo.itシミュレーションサンプル

前項で確かめたお題の「ALPHAMASK FILTER」で座標の中点を求めていたのは,このアンカーポイントの座標を決めるためだったのだ。

著者プロフィール

野中文雄(のなかふみお)

ソフトウェアトレーナー,テクニカルライター,オーサリングエンジニア。上智大学法学部卒,慶応義塾大学大学院経営管理研究科修士課程修了(MBA)。独立系パソコン販売会社で,総務・人事,企画,外資系企業担当営業などに携わる。その後,マルチメディアコンテンツ制作会社に転職。ソフトウェアトレーニング,コンテンツ制作などの業務を担当する。2001年11月に独立。Web制作者に向けた情報発信プロジェクトF-siteにも参加する。株式会社ロクナナ取締役(非常勤)。

URLhttp://www.FumioNonaka.com/

著書

コメント

コメントの記入