2007年09月20日
ベジエ曲線の仕組み (3) - 3次ベジエ曲線
ちょっと息切れしてきたのでサンプルプログラムでごまかし。
- 黒い点をドラッグすると、制御点を移動できます。
- 赤いボタンの上にマウスを置くと、1つ目の2次ベジエ曲線を描きます。
- 緑のボタンの上にマウスを置くと、2つ目の2次ベジエ曲線を描きます。
- 青いボタンの上にマウスを置くと、1つ目と2つ目のベジエ曲線の間を描きます。これが3次ベジエ曲線だよ。
3次ベジエ曲線といえども、2次ベジエ曲線を2つ書いてその間をとるだけです。
簡単ですね!
目次
- ベジエ曲線の仕組み (1) - 昔話
- ベジエ曲線の仕組み (2) - 2次ベジエ曲線を詳しく
- ベジエ曲線の仕組み (3) - 3次ベジエ曲線
- ベジエ曲線の仕組み (4) - ActionScript 3.0 でベジエ曲線を描く
ソースコード
ソースコードは以下に(270行)。ちょっと凝ったことをすると、ソースコードが長くなるなぁ。
package { import flash.display.Sprite; import flash.display.Graphics; import flash.events.TimerEvent; import flash.geom.Point; import flash.utils.Timer; import caurina.transitions.Tweener; public class BezierTest3 extends Sprite { private var p1:BezierPoint = new BezierPoint(); private var p2:BezierPoint = new BezierPoint(); private var p0:BezierPoint = new BezierPoint(p1); private var p3:BezierPoint = new BezierPoint(p2); private var animeType:int = -1; private var animeLayer:Sprite = new Sprite(); private var timer:Timer = new Timer(100); private var button1:AnimeButton = new AnimeButton(0, 0x900000); private var button2:AnimeButton = new AnimeButton(1, 0x009000); private var button3:AnimeButton = new AnimeButton(2, 0x000090); public function BezierTest3() { addChild(animeLayer); addChild(p0); addChild(p1); addChild(p2); addChild(p3); p0.x = 0; p0.y = 120; p1.x = 60; p1.y = 20; p2.x = 180; p2.y = 40; p3.x = 260; p3.y = 160; addChild(button1); button1.x = 0; button1.y = 150; addChild(button2); button2.x = 25; button2.y = 150; addChild(button3); button3.x = 50; button3.y = 150; timer.addEventListener("timer", timerHandler); addEventListener("enterFrame", function(event:*):void { drawBezier(); }); } public function setAnimation(num:int = -1):void { animeType = Math.min(num, 2); animeLayer.graphics.clear(); while(animeLayer.numChildren > 0) { animeLayer.removeChildAt(0); } timer.reset(); if(animeType >= 0) { timer.start(); } } private function timerHandler(event:TimerEvent):void { var c:int = event.target.currentCount; // initialize animation if(c == 1) { animeLayer.graphics.lineStyle(1, 0x0090ff); if(animeType == 2) { animeLayer.graphics.moveTo(p0.x, p0.y); animeLayer.graphics.curveTo(p1.x, p1.y, p2.x, p2.y); animeLayer.graphics.moveTo(p1.x, p1.y); animeLayer.graphics.curveTo(p2.x, p2.y, p3.x, p3.y); } else { drawLine(p1, p2, 0x0090ff, animeLayer.graphics); } } if(c <= 20) { var pt:Array = getBezierPoint(0.05 * c); var ptNum:int = [7, 8, 9][animeType]; var l0Num:int = [4, 5, 7][animeType]; var l1Num:int = [5, 6, 8][animeType]; var dot:Sprite = new Sprite(); dot.graphics.beginFill(0); dot.graphics.drawCircle(0, 0, 2); dot.graphics.endFill(); dot.x = pt[ptNum].x; dot.y = pt[ptNum].y; animeLayer.addChild(dot); var line:Sprite = new Sprite(); drawLine(pt[l0Num], pt[l1Num], 0x666666, line.graphics); animeLayer.addChild(line); Tweener.addTween(dot, { alpha: 0, time: 4, onComplete: function():void { removeChild(dot); } }); Tweener.addTween(line, { alpha: 0.5, _color: 0xcccccc, time: 2 }); } else { timer.stop(); } } public function drawBezier():void { graphics.clear(); drawLine(p0, p1, 0x999999); drawLine(p2, p3, 0x999999); graphics.moveTo(p0.x, p0.y); graphics.lineStyle(1, 0x800000); for(var t:Number = 0.0; t <= 1.0; t += 0.05) { var pt:Point = getBezierPoint(t)[9] graphics.lineTo(pt.x, pt.y); } graphics.lineTo(p3.x, p3.y); } private function getBezierPoint(t:Number):Array { var p4:Point = getInnerPoint(p0, p1, t); var p6:Point = getInnerPoint(p2, p3, t); var p5:Point = getInnerPoint(p1, p2, t); var p7:Point = getInnerPoint(p4, p5, t); var p8:Point = getInnerPoint(p5, p6, t); var p9:Point = getInnerPoint(p7, p8, t); return [null, null, null, null, p4, p5, p6, p7, p8, p9]; } private function getInnerPoint(p0:*, p1:*, t:Number):Point { return new Point(p0.x * (1 - t) + p1.x * t, p0.y * (1 - t) + p1.y * t); } private function drawLine(p0:*, p1:*, color:int, g:Graphics = null):void { g = g || graphics; g.lineStyle(1, color); g.moveTo(p0.x, p0.y); g.lineTo(p1.x, p1.y); g.lineStyle(); } } } import flash.display.Sprite; import flash.geom.Point; import flash.filters.BevelFilter; internal class BezierPoint extends Sprite { private const COLOR:int = 0x000000; private const RADIUS:int = 3; private var dragging:Boolean; private var diff:Point = new Point(); private var child:Sprite; public function BezierPoint(_child:Sprite = null):void { child = _child; graphics.beginFill(COLOR); graphics.drawCircle(0, 0, RADIUS); graphics.endFill(); buttonMode = true; var dragging:Boolean = false; var diff:Point = new Point(); addEventListener("mouseDown", mouseDownHandler); } private function mouseDownHandler(event:*):void { if(!dragging) { dragging = true; startDrag(); if(child) { diff.x = child.x - x; diff.y = child.y - y; } stage.addEventListener("mouseMove", mouseMoveHandler); stage.addEventListener("mouseUp", mouseUpHandler); } } private function mouseMoveHandler(event:*):void { if(dragging && child) { child.x = x + diff.x; child.y = y + diff.y; } } private function mouseUpHandler(event:*):void { if(dragging) { stopDrag(); dragging = false; removeEventListener("mouseMove", mouseMoveHandler); removeEventListener("mouseUp", mouseUpHandler); } } } internal class AnimeButton extends Sprite { private const WIDTH:int = 20; private const HEIGHT:int = 20; private var num:int; public function AnimeButton(_num:int, color:int) { num = _num; graphics.beginFill(color); graphics.drawRoundRect(0, 0, WIDTH, HEIGHT, 10, 10); graphics.endFill(); filters = [new BevelFilter(3, 45, 0xffffff, 0.6, 0x000000, 0.6)]; buttonMode = true; addEventListener("mouseOver", startAnimation); addEventListener("click", startAnimation); addEventListener("mouseOut", endAnimation); } private function startAnimation(event:*):void { var main:BezierTest3 = parent as BezierTest3; main.setAnimation(num); } private function endAnimation(event:*):void { var main:BezierTest3 = parent as BezierTest3; main.setAnimation(); } }