forked from: 木のもと forked from: Catmull-Rom スプライン曲線

by sanesashi
Catmull-Rom スプライン曲線
code from http://l00oo.oo00l.com/blog/archives/264
♥0 | Line 251 | Modified 2010-09-27 06:34:39 | MIT License
play

ActionScript3 source code

/**
 * Copyright sanesashi ( http://wonderfl.net/user/sanesashi )
 * MIT License ( http://www.opensource.org/licenses/mit-license.php )
 * Downloaded from: http://wonderfl.net/c/oEQy
 */

// forked from sanesashi's 木のもと forked from: Catmull-Rom スプライン曲線
// forked from sanesashi's forked from: Catmull-Rom スプライン曲線
// forked from sanesashi's Catmull-Rom スプライン曲線
// Catmull-Rom スプライン曲線
// code from http://l00oo.oo00l.com/blog/archives/264
package {
    import flash.display.Sprite;
    import flash.display.StageAlign;
    import flash.display.StageScaleMode;
    import flash.events.Event;
    import flash.geom.Point;
    import flash.display.Loader;
    import flash.display.LoaderInfo;
    import flash.display.Bitmap;
    import flash.display.BitmapData;
    import flash.system.LoaderContext;
    import flash.geom.Matrix;
    import flash.net.URLRequest;
    
    public class Tree extends Sprite
    {
        private var _canvasWidth:uint = 480;
        private var _offsetTop:uint = 70;
        
        private var _point:Point;
        private var _angle:Number = 0;
        private var _skinLeft:Array = [];
        private var _skinRight:Array = [];
        
        public function Tree()
        {
            if(stage) init();
            else addEventListener(Event.ADDED_TO_STAGE, init);
        }
    
        private function init(e:Event=null):void
        {
            stage.scaleMode = StageScaleMode.NO_SCALE;
            stage.align = StageAlign.TOP_LEFT;
            load();
        }

        private function load():void
        {
            var context:LoaderContext = new LoaderContext(true);
            var loader:Loader = new Loader();
            loader.contentLoaderInfo.addEventListener(Event.COMPLETE, draw);
            loader.load(new URLRequest("http://farm5.static.flickr.com/4110/5011491754_be6696dfe1.jpg"), new LoaderContext(true));
        }
        
        private function draw(e:Event):void
        {
            var lineColor:Number = 0x222222;// 縁取り線の色
            var lineWidth:Number = 2;// 縁取り線の太さ
            
            var loader:Loader = LoaderInfo(e.target).loader;
            var texture:BitmapData = Bitmap(loader.content).bitmapData;

            this.trunkTop();
            this.trunkMiddle();
            this.trunkBottom();
            
            // 先端が配列の中央にくるように配列を結合する
            var points:Array = this._skinLeft.reverse().concat(this._skinRight);

            (new Texture(this)).draw(texture, points);
            (new Spline(this)).draw(this._skinLeft, lineWidth, lineColor);
            (new Spline(this)).draw(this._skinRight, lineWidth, lineColor);
        }

        // 幹の上部
        private function trunkTop():void
        {
            var p:Point = new Point(this._canvasWidth * 0.52, this._offsetTop);// 開始点
            var phase:Number = Math.random() > 0.5 ? 0 : Math.PI/0.9;// 初期位相
            var angle:Number = - Math.PI * 0.9;// 伸びる方向
            const numJoints:Number = 6;// 節の数
            var jointLength:Number = this._canvasWidth/numJoints * 0.3;// 節の長さ(初期値)
            var skinLeft:Point;
            var skinRight:Point;

            for(var i:int=0; i < numJoints; i++){
                // 幹の次の点
                jointLength = jointLength + Math.random()*jointLength/8;
                angle = angle + 0.6*Math.sin(Math.PI*i*0.95 + phase) + (Math.random()-0.5)*0.2;
                var q:Point = this.nextPoint(p, angle, jointLength);
                
                // 幹の骨から皮の点を求める
                skinLeft  = this.skinTop(p, angle, 1, jointLength, i);
                skinRight = this.skinTop(p, angle, -1, jointLength, i);
                
                this._skinLeft.push(skinLeft);
                this._skinRight.push(skinRight);

                p = q;
            }
            // 引継ぎ
            this._point = p;
            this._angle = angle;
        }
        
        private function trunkMiddle():void
        {
            var p:Point = this._point;// 開始点はtrunkTopの終了点
            var phase:Number = 0;// 初期位相
            var angle:Number = -Math.PI * 0.3;// 伸びる方向
            const numJoints:Number = 9;// 節の数
            var jointLength:Number = this._canvasWidth/16;// 節の長さ(初期値)
            var skinLeft:Point;
            var skinRight:Point;
            var angleDiff:Number = angle - this._angle;

            for(var i:int=0; i < numJoints; i++){
                // 幹の次の点
                if(i < 5){
                    // はじめの5回で徐々に角度をならしていく
                    jointLength = this._canvasWidth/16 + i * jointLength/16 * Math.random();
                    angle = this._angle + angleDiff/5*i ;//+ (Math.random()-0.5)*0.3;
                }else{
                    jointLength = jointLength + Math.random()*jointLength/6;
                    angle = angle + 0.5*Math.sin(Math.PI*i*0.9 + phase) + (Math.random()-0.5)*0.1;
                }
                var q:Point = this.nextPoint(p, angle, jointLength);
                
                // 幹の骨から皮の点を求める
                skinLeft  = this.skinMiddle(p, angle, 1, jointLength, i);
                skinRight = this.skinMiddle(p, angle, -1, jointLength, i);
                
                this._skinLeft.push(skinLeft);
                this._skinRight.push(skinRight);
                
                if(i==0){
                    this.plot(p);
                    this.plot(skinLeft);
                    this.plot(skinRight);
                }
                p = q;
            }
            // 引継ぎ
            this._point = p;
            this._angle = angle;
        }
        

        private function trunkBottom():void
        {
            var p:Point = this._point;// 開始点はtrunkTopの終了点
            var phase:Number = 0;// 初期位相
            var angle:Number = -Math.PI * 0.3;// 伸びる方向
            const numJoints:Number = 20;// 節の数
            var jointLength:Number = this._canvasWidth/6;// 節の長さ(初期値)
            var skinLeft:Point;
            var skinRight:Point;
            var angleDiff:Number = angle - this._angle;
            
            // アドホックな調整
            p.x += jointLength/4;
            p.y += jointLength/2;
            
            for(var i:int=0; i < numJoints; i++){
                // 幹の次の点
                if(i < 3){
                    // 徐々に角度をならしていく
                    jointLength = this._canvasWidth/6 + i * jointLength/8 * Math.random();
                    angle = this._angle + angleDiff * 0.8 *i ;//+ (Math.random()-0.5)*0.3;
                }else{
                    jointLength = jointLength * 1.2;
                    angle = angle + 0.4*Math.sin(Math.PI*i*0.3 + phase) + (Math.random()-1)*0.1;
                }
                var q:Point = this.nextPoint(p, angle, jointLength);

                // 幹の骨から皮の点を求める
                skinLeft  = this.skinBottom(p, angle, 1, jointLength, i);
                skinRight = this.skinBottom(p, angle, -1, jointLength, i);
                
                this._skinLeft.push(skinLeft);
                this._skinRight.push(skinRight);
                
                if(i>=0){
                    this.plot(p);
                    this.plot(skinLeft);
                    this.plot(skinRight);
                }
                p = q;
            }   
        }
        
        // 幹の骨から皮の点を求める
        private function skinTop(pointTrunk:Point, angleTrunk:Number, dir:Number, d:Number, i:Number):Point
        {
            var angle:Number = angleTrunk + dir * Math.PI/2;
            var distance:Number = d/32 + d/16*i + d/32 * Math.random();
            return this.nextPoint(pointTrunk, angle, distance);
        }

        private function skinMiddle(pointTrunk:Point, angleTrunk:Number, dir:Number, d:Number, i:Number):Point
        {
            var angle:Number = angleTrunk + dir * Math.PI/2;
            var distance:Number = 0;
            if(i < 3){
                distance = d/2 + d/4*i + d/16 * Math.random();
            }else{
                distance = d + d/20*i + d/16 * Math.random();
            }
            return this.nextPoint(pointTrunk, angle, distance);
        }
        private function skinBottom(pointTrunk:Point, angleTrunk:Number, dir:Number, d:Number, i:Number):Point
        {
            var angle:Number = angleTrunk + dir * Math.PI/2;
            var distance:Number = 0;
            if(i < 2){
                distance = d + d/8*i + d/16 * Math.random();
            }else{
                distance = d * 1.1 + d/8 * Math.random();
            }
            return this.nextPoint(pointTrunk, angle, distance);
        }
        // 点Pから角度a,距離dの点Qを求める
        private function nextPoint(p:Point, angle:Number, distance:Number):Point
        {
            var q:Point = new Point();
            q.x = p.x + distance * Math.cos(angle);
            q.y = p.y - distance * Math.sin(angle);     
            return q;
        }

        private function plot(p:Point):void
        {
            var s:Sprite = new Sprite();
            s.x = p.x;
            s.y = p.y;
            s.graphics.beginFill(0xff3366);
            s.graphics.drawCircle(0, 0, 2);
            s.graphics.endFill();
            addChild(s);
        }
    }
}

import flash.display.Sprite;
import flash.geom.Point;
import flash.display.BitmapData;

// catmullスプライン曲線
class Spline
{
    private var _sprite:Sprite
    private var _numSegments:uint;//曲線分割数(大きくするとなめらかになる)
    
    public function Spline(sprite:Sprite, numSegments:uint = 5):void
    {
        this._sprite = sprite;
        this._numSegments = numSegments;
    }
    
    public function draw(v:Array, width:uint, color:uint):void
    {
        if(v.length<2) return;
        v.splice(0,0,v[0]);
        v.push(v[v.length-1]);
    
        for(var i:uint=0; i<v.length-3; i++){
            var p0:Point = v[i];
            var p1:Point = v[i+1];
            var p2:Point = v[i+2];
            var p3:Point = v[i+3];
            splineTo(width, color, p0, p1, p2, p3);
        }
    }
    
    private function splineTo(width:uint, color:uint, p0:Point, p1:Point, p2:Point, p3:Point):void
    {
        this._sprite.graphics.lineStyle(width, color);
        this._sprite.graphics.moveTo(p1.x, p1.y);
        
        // 直線数本つなげて曲線っぽく
        for(var i:uint = 0; i < this._numSegments; i++){
            var t:Number = (i+1) / this._numSegments;
            this._sprite.graphics.lineTo(
                catmullRom(p0.x, p1.x, p2.x, p3.x, t),
                catmullRom(p0.y, p1.y, p2.y, p3.y, t)
            );
        }
        
        // 最後に起点から終点に直線も引く(といい感じの線になる)
        this._sprite.graphics.moveTo(p1.x, p1.y);
        this._sprite.graphics.lineTo(p2.x, p2.y);
    }
    
    private function catmullRom(p0:Number, p1:Number, p2:Number, p3:Number, t:Number):Number
    {
        var v0:Number = (p2 - p0) * 0.5;
        var v1:Number = (p3 - p1) * 0.5;
        return (2*p1 - 2*p2 + v0 + v1)*t*t*t + (-3*p1 + 3*p2 - 2*v0 - v1)*t*t + v0*t + p1;
    }
}

class Texture
{
    private var _sprite:Sprite
    
    public function Texture(sprite:Sprite):void
    {
        this._sprite = sprite;
    }
    public function draw(texture:BitmapData, points:Array):void
    {
        // bitmapで塗る
        this._sprite.graphics.beginBitmapFill(texture);
        this._sprite.graphics.moveTo(points[0].x, points[0].y);
        for(var i:uint=0; i < points.length; i++){
            this._sprite.graphics.lineTo(points[i].x, points[i].y);
        }
        this._sprite.graphics.endFill();
    }
}