ベジエ曲線の仕組み (3) - 3次ベジエ曲線

ちょっと息切れしてきたのでサンプルプログラムでごまかし。

  • 黒い点をドラッグすると、制御点を移動できます。
  • 赤いボタンの上にマウスを置くと、1つ目の2次ベジエ曲線を描きます。
  • 緑のボタンの上にマウスを置くと、2つ目の2次ベジエ曲線を描きます。
  • 青いボタンの上にマウスを置くと、1つ目と2つ目のベジエ曲線の間を描きます。これが3次ベジエ曲線だよ。

3次ベジエ曲線といえども、2次ベジエ曲線を2つ書いてその間をとるだけです。

簡単ですね!

目次

  1. ベジエ曲線の仕組み (1) - 昔話
  2. ベジエ曲線の仕組み (2) - 2次ベジエ曲線を詳しく
  3. ベジエ曲線の仕組み (3) - 3次ベジエ曲線
  4. ベジエ曲線の仕組み (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();
    }
}