滑らかな3次ベジェでゆらゆら曲線を描く

FICC 福岡です。

珍しくやる気になりましてAS3関連のエントリー。個人的にゆらゆらした円が描きたくなり、

こんなのが作りたい

こんな図を描き出す方法を考えてみました。Flashで滑らかな曲線、といえばgraphics.curveTo()ですが、このcurveToは2次ベジェ曲線しか書く事ができません。ベジェ曲線の詳しい話は省略しますが、できればIllustratorのようにハンドリングが楽な3次ベジェが使いたい。今回はアンカーポイントとコントロールポイントの位置から3次ベジェを計算し、2次ベジェに変換してFlashで描画します。幸いFlashCS3のfl.motion.BezierSegmentクラスが面倒な計算を助けてくれますのでこのクラスを有効活用します。

図解3次ベジェ

BezierSegmentを使うには、まず3次ベジェの要素である4つのポイント(上図でのp0,p1,p2,p3)を用います。

var bezierSegment:BezierSegment = new BezierSegment(p0,p1,p2,p3);

例えば上図中の赤い点の座標を知りたい場合、赤い点は線分のおよそ中間地点(t=0.5)にいますので赤い点の座標(Point)は

var point:Point = bezierSegment.getValue(0.5);

で求める事ができます。これを踏まえて完成したのがこんなswfです。

クリックすると曲線の形が変わります。また、交互にコントロールバーがまっすぐな場合と折れている場合で切り替わります。滑らかなベジェ曲線を描く場合はアンカーポイントと2つのコントロールポイントが一直線に並んでいなければいけません。今回書いたコードではアンカーポイントと1つのコントロールポイントの座標をランダムで決定し、そこからもう1つのコントロールポイントの座標を決定しています。

package {
import flash.display.Sprite;
import flash.display.Graphics;
import flash.text.TextField;
import flash.events.MouseEvent;
import flash.geom.Point;
import fl.motion.BezierSegment;
	
public class YurayuraCircle extends Sprite {
private var container:Sprite = new Sprite();
private var pointArray:Array = new Array(); //ポイントを格納するArray
private var anchorNum:int = 5; //アンカーポイントの数
private var totalPointNum:int = anchorNum*3; //アンカーポイントとコントロールポイントの総数
private var smoothing:Boolean = true;
private var outlineColor:Number = 0x4F80FF;
	
public function yurayuraCircle() {
stage.addChild(container);
container.x = 250;
container.y = 250;
createPointArray()
drawLine();
var txt:TextField = new TextField();
txt.text = "クリックで再描画";
stage.addChild(txt);
stage.addEventListener(MouseEvent.CLICK,onClick); // クリックで再描画
}
private function createPointArray():void{
var array:Array = pointArray;
var prevNum1:int;
var prevNum2:int;
//
array[totalPointNum-1] = createPoint(totalPointNum-1)
for (var i:int=0; i < totalPointNum-1; i++) {
if(i%3 == 1 && smoothing){
prevNum1 = i-1;
prevNum2 = i-2;
if(prevNum2<0){
prevNum2 = totalPointNum-1;
}
//アンカーポイントとコントロールポイントからもう一つのコントロールポイントの座標を割り出す
//(ただしこの場合アンカーポイントは2つのコントロールポイントの中点とする)
array[i] = new Point(2*array[prevNum1].x-array[prevNum2].x,2*array[prevNum1].y-array[prevNum2].y);
}else{
array[i] = createPoint(i)
}
}
}
private function createPoint(num:int):Point{
var r:Number = 150;
var rad:Number = num*(2*Math.PI)/totalPointNum;
var ran:Number = Math.random();
var point:Point = new Point(Math.sin(rad)*r*(0.5+0.8*ran) , Math.cos(rad)*r*(0.5+0.8*ran));
return point;
}
private function drawLine():void {
var array:Array =pointArray;
var point1:Number;
var point2:Number;
var anchor1:Number;
var anchor2:Number;
container.graphics.clear();
for (var i:int=0; i
point1 = i;
if (i%3 == 0) {
point2 = i+3;
anchor1 = i+1;
anchor2 = i+2;
if (point2 >= totalPointNum) {
point2 = 0;
}
drawBezier(array[point1],array[anchor1],array[anchor2],array[point2])
//アンカーポイントの四角ぽち&コントロールバー描画
container.graphics.lineStyle(1, outlineColor,1);
container.graphics.drawRect(array[point1].x-2,array[point1].y-2,4,4);
container.graphics.endFill();
container.graphics.lineStyle(1, outlineColor,1);
container.graphics.beginFill(0x000000,0);
container.graphics.moveTo(array[point1].x, array[point1].y);
container.graphics.lineTo(array[anchor1].x, array[anchor1].y);
container.graphics.moveTo(array[anchor2].x, array[anchor2].y);
container.graphics.lineTo(array[point2].x, array[point2].y);
container.graphics.endFill();
}else{
//コントロールポイントの丸ぽち描画
container.graphics.lineStyle(0, 0x000000,0);
container.graphics.beginFill(outlineColor,1);
container.graphics.drawCircle(array[point1].x,array[point1].y,2);
container.graphics.endFill();
}
}
}
public function drawBezier(p0:Point,p1:Point,p2:Point,p3:Point):void{
var ancorPoint1:Point
var ancorPoint2:Point
var ancorPoint3:Point
var controllPoint:Point;
var t:Number;
	
var separate:Number = 4; // ベジェの分割数
var sep:Number = 1.0 / separate;
container.graphics.moveTo(p0.x, p0.y);
container.graphics.lineStyle(1, 0x000000,1);
// BezierSegmentを用意
var bezierSegment:BezierSegment = new BezierSegment(p0,p1,p2,p3);
for(t = sep * 2; t <= 1.0; t += sep * 2){
ancorPoint1 = bezierSegment.getValue(t-sep*2);
ancorPoint2 = bezierSegment.getValue(t-sep);
ancorPoint3 = bezierSegment.getValue(t);
//ancorPoint2を通るようにcontrollPointを設定
controllPoint = new Point(2 * ancorPoint2.x - (ancorPoint1.x + ancorPoint3.x) * 0.5, 2 * ancorPoint2.y - (ancorPoint1.y + ancorPoint3.y) * 0.5);
//ancorPoint1からancorPoint3への2次ベジェを描画
container.graphics.curveTo(controllPoint.x, controllPoint.y, ancorPoint3.x, ancorPoint3.y);
}
}
private function onClick(event:MouseEvent):void{
if(smoothing){
smoothing = false;
outlineColor = 0xFF4F4F;
}else{
smoothing = true;
outlineColor = 0x4F80FF
}
createPointArray()
drawLine();
}
}
}

なお今回のエントリーを書くにあたって
ベジエ曲線の仕組み (4) - ActionScript 3.0 でベジエ曲線を描く - てっく煮ブログ
を参考にさせていただきました。ありがとうございました。

トラックバック(0)

このブログ記事を参照しているブログ一覧: 滑らかな3次ベジェでゆらゆら曲線を描く

このブログ記事に対するトラックバックURL: http://www.ficc.jp/cgi-bin/mt4/mt-tb.cgi/19

コメント(1)

気持ちいいですね
次の波紋の「形が想像できない」のと「動きが想像できる」のと、
バランス感覚が面白くて、なんだかわからないけど、見てしまいます

コメントする