forked from: フリーハンドベジェ

by aobyrne forked from フリーハンドベジェ (diff: 404)
mashed:
forked (that is : http://wonderfl.net/c/srIi )
and
http://wonderfl.net/c/ubO1
♥0 | Line 944 | Modified 2012-05-02 22:26:50 | GPLv3 License
play

ActionScript3 source code

/**
 * Copyright aobyrne ( http://wonderfl.net/user/aobyrne )
 * GNU General Public License, v3 ( http://www.gnu.org/licenses/quick-guide-gplv3.html )
 * Downloaded from: http://wonderfl.net/c/srIi
 */

/**
 * フリーハンドベジェAS3移植版
 * (ドラッグ操作で絵がかけます。)
 *
 * Released under the GPL
 *
 * refered from
 * http://childtv.web.fc2.com/bezier/
 */
package
{
    import flash.display.Sprite;
    import flash.events.Event;

    [SWF(frameRate="60", backgroundColor="0xffffff")]
    public class BezierSoftDrawing extends Sprite
    {

        public function BezierSoftDrawing()
        {
            loaderInfo.addEventListener(Event.COMPLETE, completeHandler);
        }

        private function completeHandler(e:Event):void
        {
            loaderInfo.removeEventListener(Event.COMPLETE, completeHandler);
            addChild(new BezierCanvas(loaderInfo.width, loaderInfo.height));
            
        }
    }
}

import flash.display.DisplayObjectContainer;
import flash.display.Graphics;
import flash.display.Shape;
import flash.display.Sprite;
import flash.events.Event;
import flash.events.MouseEvent;
import flash.events.TimerEvent;
import flash.geom.Point;
import flash.utils.Timer;

class BezierCanvas extends Sprite
{
    private var _drawingPath:Path;

    private var _fixedPath:Path;

    private var _generator:BezierGenerator;

    private var _lastHeight:Number;

    private var _lastPoint:BezierPoint;

    private var _lastWidth:Number;

    private var _offsetX:Number;

    private var _offsetY:Number;

    private var _paths:Array;

    private var _points:Array;

    private var _tangent:BezierPoint;
    private var wanderingTarget:WanderingTarget;
    private var wPoint:Point;

    public function BezierCanvas(w:Number, h:Number)
    {
        _generator = new BezierGenerator({tolerance: 2.5, maxIterations: 1});
        _paths = [];
        mouseChildren = false;
        //addEventListener(MouseEvent.MOUSE_DOWN, mouseDownHandler);

        var g:Graphics = graphics;
        g.clear();
        g.beginFill(0, 0);
        g.drawRect(0, 0, w, h);
        g.endFill();
        
        wanderingTarget = new WanderingTarget;
        var canvasHeight :Number = 465;
        var canvasWidth:Number = 465;
        wanderingTarget.position = new Vector3D(canvasHeight * 0.5,canvasHeight * 0.5);
        wanderingTarget.boundsCentre = new Vector3D(canvasHeight*0.5, canvasWidth*0.5, 0);
        wanderingTarget.boundsRadius = canvasHeight * 0.5;
        
        var timer:Timer = new Timer(100);
        timer.addEventListener(TimerEvent.TIMER, ot);
        timer.start();
        wPoint = new Point;
    }
    
    private function ot(e:TimerEvent):void 
    {
        var timer:Timer = e.target as Timer;
        var number:int = timer.currentCount % 50;
        wanderingTarget.wander();
        wanderingTarget.update();
        wPoint.x = wanderingTarget.x;
        wPoint.y = wanderingTarget.y;
        trace( "wPoint : " + wPoint );
        if (number==1) //mouse down
        {
            startLine(wPoint);
        }
        else if (number == 0)//mouse up
        {
            addPoint(toPointFromPoint(wPoint));

            if (_drawingPath === null)
            {
                _fixedPath.erase();
                return;
            }

            _drawingPath.erase();
            _fixedPath.append(_drawingPath);
            _paths.push(_fixedPath);            
        }
        else
        {
            addPoint(toPointFromPoint(wPoint));
        }
    }

    public function redraw(segment:Array):void
    {
        if (_drawingPath !== null)
        {
            _drawingPath.clear();
        }
        else
        {
            _drawingPath = new Path(this);
        }

        _drawingPath.moveTo(segment[0]);
        _drawingPath.curveTo(segment[1], segment[2], segment[3]);
        _drawingPath.draw();
    }

    private function addPoint(point:BezierPoint):void
    {
        if (point.equals(_lastPoint))
        {
            return;
        }

        _lastPoint = point;
        _points.push(point);

        var segment:Array = _generator.fromPoints(_points, _tangent);
        if (segment.length === 0)
        {
            _drawingPath.erase();
            _fixedPath.append(_drawingPath);
            _tangent = _fixedPath.smoothTangent();
            _points = _points.slice(_points.length - 2);
            segment = _generator.fromPoints(_points, _tangent);
        }
        redraw(segment);
    }

    private function mouseDownHandler(e:MouseEvent):void
    {
        stage.addEventListener(MouseEvent.MOUSE_UP, mouseHandler);
        stage.addEventListener(MouseEvent.MOUSE_MOVE, mouseHandler);

        var p:Point = new Point(e.stageX, e.stageY);
        trace( "p : " + p );
        p = globalToLocal(p);
        trace( "p : " + p );
        _offsetX = e.stageX - p.x;
        trace( "_offsetX : " + _offsetX );
        _offsetY = e.stageY - p.y;
        trace( "_offsetY : " + _offsetY );

        _lastPoint = null;
        _tangent = null;

        var point:BezierPoint = toPoint(e);
        _points = [point];
        _fixedPath = new Path(this);
        _fixedPath.moveTo(point);
        _fixedPath.draw();
        _drawingPath = null;
    }
    
    private function startLine(pt:Point):void 
    {
        var p:Point = new Point(pt.x, pt.y);
        p = globalToLocal(p);
        _offsetX = 0;// e.stageX - p.x;
        _offsetY = 0;// e.stageY - p.y;

        _lastPoint = null;
        _tangent = null;

        var point:BezierPoint = toPointFromPoint(pt);
        _points = [point];
        _fixedPath = new Path(this);
        _fixedPath.moveTo(point);
        _fixedPath.draw();
        _drawingPath = null;
        
    }

    private function mouseHandler(e:MouseEvent):void
    {
        if (e.type === MouseEvent.MOUSE_UP)
        {
            stage.removeEventListener(MouseEvent.MOUSE_UP, mouseHandler);
            stage.removeEventListener(MouseEvent.MOUSE_MOVE, mouseHandler);
            addPoint(toPoint(e));

            if (_drawingPath === null)
            {
                _fixedPath.erase();
                return;
            }

            _drawingPath.erase();
            _fixedPath.append(_drawingPath);
            _paths.push(_fixedPath);
            return;
        }

        addPoint(toPoint(e));
        e.updateAfterEvent();
    }
    
    

    private function toPoint(e:MouseEvent):BezierPoint
    {
        var x:Number = e.stageX - _offsetX;
        var y:Number = e.stageY - _offsetY;

        return new BezierPoint(x, y);
    }
    
    private function toPointFromPoint(pt:Point):BezierPoint
    {
        var x:Number = pt.x - _offsetX;
        var y:Number = pt.y - _offsetY;

        return new BezierPoint(x, y);
    }
}

class Path extends Shape
{

    private var _lastPoint:BezierPoint;

    private var _pointSet:Array;

    private var _target:DisplayObjectContainer;

    public function Path(target:DisplayObjectContainer)
    {
        setupLineStyle();
        _pointSet = [];
        _target = target;
    }

    public function append(path:Path):void
    {
        var pointSet:Array = path._pointSet;
        for (var i:int = 1, len:int = pointSet.length; i < len; i++)
        {
            var points:Array = pointSet[i];
            if (points.length === 3)
            {
                curveTo(points[0], points[1], points[2]);
            }
        }
    }

    public function clear():void
    {
        _pointSet = [];
        graphics.clear();
        setupLineStyle();
    }

    public function curveTo(control1:BezierPoint, control2:BezierPoint, point:BezierPoint):void
    {
        _pointSet.push([control1, control2, point]);

        var g:Graphics = graphics;
        var x0:Number = _lastPoint.x;
        var y0:Number = _lastPoint.y;
        var x1:Number = control1.x * 3;
        var y1:Number = control1.y * 3;
        var x2:Number = control2.x * 3;
        var y2:Number = control2.y * 3;
        var x3:Number = point.x;
        var y3:Number = point.y;
        var u:Number = 1.0 / 50;
        var t1:Number, t0:Number;
        var x:Number, y:Number;

        _lastPoint = point;

        for (t0 = 0, t1 = 1; t0 < 1; t0 += u, t1 = 1 - t0)
        {
            x = (x0 * t1 * t1 * t1) +
                (x1 * t0 * t1 * t1) +
                (x2 * t0 * t0 * t1) +
                (x3 * t0 * t0 * t0);
            y = (y0 * t1 * t1 * t1) +
                (y1 * t0 * t1 * t1) +
                (y2 * t0 * t0 * t1) +
                (y3 * t0 * t0 * t0);
            g.lineTo(x, y);
        }

        g.lineTo(x3, y3);
    }

    public function draw():void
    {
        if (parent === null)
        {
            _target.addChild(this);
        }
    }

    public function erase():void
    {
        if (parent !== null)
        {
            _target.removeChild(this);
        }
    }

    public function moveTo(point:BezierPoint):void
    {
        _lastPoint = point;
        _pointSet.push([point]);
        graphics.moveTo(point.x, point.y);
    }

    public function setupLineStyle():void
    {
        graphics.lineStyle(3, 0, 1, false)
    }

    public function smoothTangent():BezierPoint
    {
        var points:Array = _pointSet[_pointSet.length - 1];
        if (points !== null && points.length !== 3)
        {
            return null;
        }

        return points[2].minus(points[1]).unit;
    }
}

class BezierPoint
{

    public static const ZERO_POINT:BezierPoint = new BezierPoint(0, 0);

    public static function operate(operator:Function, ... points):BezierPoint
    {
        var x:Array = [], y:Array = [];
        for (var i:int = 0, len:int = points.length; i < len; i++)
        {
            x[i] = points[i].x;
            y[i] = points[i].y;
        }

        return new BezierPoint(operator.apply(null, x), operator.apply(null, y));
    }

    private var _x:Number;

    private var _y:Number;

    private var _zero:Boolean;

    public function BezierPoint(x:Number, y:Number)
    {
        _x = x;
        _y = y;
        _zero = _x === 0 && _y === 0;
    }

    public function get abs():Number
    {
        return Math.sqrt(_x * _x + _y * _y);
    }

    public function distance(p:BezierPoint):Number
    {
        return new BezierPoint(_x - p._x, _y - p._y).abs;
    }

    public function dot(p:BezierPoint):Number
    {
        return _x * p._x + _y * p._y;
    }

    public function equals(p:BezierPoint):Boolean
    {
        if (p === null)
        {
            return false;
        }
        return _x === p._x && _y === p._y;
    }

    public function minus(p:BezierPoint):BezierPoint
    {
        return new BezierPoint(_x - p._x, _y - p._y);
    }

    public function plus(p:BezierPoint):BezierPoint
    {
        return new BezierPoint(_x + p._x, _y + p._y);
    }

    public function get square():Number
    {
        return _x * _x + _y * _y;
    }

    public function time(value:Number):BezierPoint
    {
        return new BezierPoint(_x * value, _y * value);
    }

    public function toString():String
    {
        return "(" + x + ", " + y + ")";
    }

    public function get unit():BezierPoint
    {
        var a:Number = abs;
        return new BezierPoint(_x / a, _y / a);
    }

    public function get x():Number
    {
        return _x;
    }

    public function get y():Number
    {
        return _y;
    }

    public function get zero():Boolean
    {
        return _zero;
    }
}

class BezierGenerator
{

    private static const PASCAL:Array = [[1], [1, 1], [1, 2, 1], [1, 3, 3, 1]];

    private var _bezier:Array;

    private var _data:Array;

    private var _endTangent:BezierPoint;

    private var _len:int;

    private var _maxIterations:Number;

    private var _params:Array;

    private var _splitBezierPoint:int;

    private var _startTangent:BezierPoint;

    private var _tolerance:Number;

    public function BezierGenerator(option:*)
    {
        option = option || {};
        _maxIterations = option.maxIterations || 3,
            _tolerance = option.tolerance || 1;
    }

    public function fromPoints(points:Array, start:BezierPoint, end:BezierPoint = null):Array
    {
        setup(points, start, end);
        var result:Array = _fromPoints();
        free();

        return result;
    }

    private function _fromPoints():Array
    {
        if (_len === 2)
        {
            return fromTwoPoints();
        }

        return generateBezier();
    }

    private function bezier(u:Number):Array
    {
        var u1:Number = 1 - u;
        var u2:Number = u1 * u1;
        var u3:Number = u * u;

        return [u2 * u1, 3 * u * u2, 3 * u3 * u1, u3 * u];
    }

    private function bezierPt(b:Array, t:Number):BezierPoint
    {
        var s:Number = 1 - t;
        var degree:int = b.length - 1;
        var spow:Array = [1];
        var tpow:Array = [1];

        for (var d:int = 0; d < degree; d++)
        {
            spow[d + 1] = spow[d] * s;
            tpow[d + 1] = tpow[d] * t;
        }

        var result:BezierPoint = b[0].time(spow[degree]);
        for (d = 1; d <= degree; d++)
        {
            result = result.plus(b[d].time(PASCAL[degree][d] * spow[degree - d] * tpow[d]));
        }

        return result;
    }

    private function computeHook(point1:BezierPoint, point2:BezierPoint, param:Number):Number
    {
        var toleranceMore:Number = _tolerance;
        var operator:Function = function(p1:Number, p2:Number, t:Number):Number
            {
                return (p1 + p2) / 2 - t;
            };
        var distance:Number = BezierPoint.operate(operator, point1, point2, bezierPt(_bezier, param)).abs;

        return (distance < toleranceMore) ? 0 : distance / (point1.distance(point2) + toleranceMore);
    }

    private function estimateEndTangent():BezierPoint
    {
        var tangent:BezierPoint;
        for (var i:int = _len - 2; i >= 0; i--)
        {
            tangent = _data[i].minus(_data[_len - 1]);
            if (tangent.square > _tolerance * _tolerance)
            {
                return tangent.unit;
            }
        }

        return tangent.zero ? _data[_len - 2].minus(_data[_len - 1]).unit : tangent.unit;
    }

    private function estimateStartTangent():BezierPoint
    {
        var tangent:BezierPoint;
        for (var i:int = 1; i < _len; i++)
        {
            tangent = _data[i].minus(_data[0]);
            if (tangent.square > _tolerance * _tolerance)
            {
                return tangent.unit;
            }
        }

        return tangent.zero ? _data[1].minus(_data[0]).unit : tangent.unit;
    }

    private function free():void
    {
        _bezier = null;
        _data = null;
        _params = null;
        _startTangent = null;
        _endTangent = null;
    }

    private function fromTwoPoints():Array
    {
        _bezier[0] = _data[0];
        _bezier[3] = _data[1];

        var distance:Number = _bezier[0].distance(_bezier[3]) / 3;
        var op1:Function = function(a:Number, b:Number):Number
            {
                return (a * 2 + b) / 3;
            };
        var op2:Function = function(a:Number, b:Number):Number
            {
                return a + b * distance;
            };

        _bezier[1] = _startTangent.zero
            ? BezierPoint.operate(op1, _bezier[0], _bezier[3])
            : BezierPoint.operate(op2, _bezier[0], _startTangent);

        _bezier[2] = _endTangent.zero
            ? BezierPoint.operate(op1, _bezier[3], _bezier[0])
            : BezierPoint.operate(op2, _bezier[3], _endTangent);

        return _bezier;
    }

    private function generateBezier():Array
    {
        setParamsByLength();
        setAssumptBezier();
        var error:Number = maxError();
        if (Math.abs(error) <= 1)
            return _bezier;

        if (error >= 0 && error <= 3)
        {
            for (var i:int = 0, len:int = _maxIterations; i < len; i++)
            {
                setAssumptBezier();
                error = maxError();
                if (Math.abs(error) <= 1)
                    return _bezier;
            }
        }
        if (error < 0)
        {
            if (_splitBezierPoint == 0 && !_startTangent.zero)
                return fromPoints(_data, BezierPoint.ZERO_POINT, _endTangent);
            if (_splitBezierPoint == _len - 1 && !_endTangent.zero)
                return fromPoints(_data, _startTangent, BezierPoint.ZERO_POINT);
        }

        return [];
    }

    private function maxError():Number
    {
        var toleranceMore:Number = _tolerance;
        var maxDistanceSquare:Number = 0;
        var maxHook:Number = 0;
        var snapEnd:int = 0;
        var prevBezierPoint:BezierPoint = _bezier[0];
        for (var i:int = 1, len:int = _len; i < len; i++)
        {
            var currentBezierPoint:BezierPoint = bezierPt(_bezier, _params[i]);
            var distanceSquare:Number = currentBezierPoint.minus(_data[i]).square;
            if (distanceSquare > maxDistanceSquare)
            {
                maxDistanceSquare = distanceSquare;
                _splitBezierPoint = i;
            }
            var hook:Number = computeHook(prevBezierPoint, currentBezierPoint, (_params[i] + _params[i - 1]) / 2);
            if (hook > maxHook)
            {
                maxHook = hook;
                snapEnd = i;
            }
            prevBezierPoint = currentBezierPoint;
        }
        var maxDistanceRatio:Number = Math.sqrt(maxDistanceSquare) / toleranceMore;
        if (maxHook <= maxDistanceRatio)
        {
            return maxDistanceRatio;
        }
        else
        {
            _splitBezierPoint = snapEnd - 1;
            return -maxHook;
        }
    }

    private function newtonRaphsonRootFind(bezier:Array, point:BezierPoint, param:Number):Number
    {
        var dbezier:Array = pointDifference(bezier);
        var ddbezier:Array = pointDifference(dbezier);
        var p:BezierPoint = bezierPt(bezier, param);
        var dp:BezierPoint = bezierPt(dbezier, param);
        var ddp:BezierPoint = bezierPt(ddbezier, param);
        var diff:BezierPoint = p.minus(point);
        var numerator:Number = diff.dot(dp);
        var denominator:Number = dp.square + diff.dot(ddp);
        var improvedParam:Number;

        if (denominator > 0)
        {
            improvedParam = param - (numerator / denominator);
        }
        else
        {
            if (numerator > 0)
            {
                improvedParam = param * 0.98 - 0.01;
            }
            else if (numerator < 0)
            {
                improvedParam = param * 0.98 + 0.031;
            }
            else
            {
                improvedParam = param;
            }
        }

        if (improvedParam < 0)
        {
            improvedParam = 0;
        }
        else if (improvedParam > 1)
        {
            improvedParam = 1;
        }

        var diffSquare:Number = diff.square;
        for (var proportion:Number = 0.125; ; proportion += 0.125)
        {
            if (bezierPt(bezier, improvedParam).minus(point).square > diffSquare)
            {
                if (proportion > 1)
                {
                    improvedParam = param;
                    break;
                }
                improvedParam = (1 - proportion) * improvedParam + proportion * param;
            }
            else
            {
                break;
            }
        }
        return improvedParam;
    }

    private function pointDifference(points:Array):Array
    {
        var diff:Array = [];
        for (var i:int = 0, len:int = points.length; i < len - 1; i++)
        {
            diff[i] = points[i + 1].minus(points[i]).time(len - 1);
        }

        return diff;
    }

    private function reparameterize():void
    {
        for (var i:int = 0, len:int = _len; i < len - 1; i++)
        {
            _params[i] = newtonRaphsonRootFind(_bezier, _data[i], _params[i]);
        }
    }

    private function setAssumptBezier():void
    {
        var start:BezierPoint = _startTangent.zero ? estimateStartTangent() : _startTangent;
        var end:BezierPoint = _endTangent.zero ? estimateEndTangent() : _endTangent;
        setAssumptBezierWithTangent(start, end);
        if (_startTangent.zero)
        {
            setControllBezierPoint(1);
            if (!_bezier[0].equals(_bezier[1]))
            {
                start = _bezier[1].minus(_bezier[0]).unit;
            }
            setAssumptBezierWithTangent(start, end);
        }
        reparameterize();
    }

    private function setAssumptBezierWithTangent(start:BezierPoint, end:BezierPoint):void
    {
        var C:Array = [[0, 0], [0, 0]];
        var X:Array = [0, 0];
        _bezier[0] = _data[0];
        _bezier[3] = _data[_len - 1];

        for (var i:int = 0; i < _len; i++)
        {
            var b:Array = bezier(_params[i]);
            var a:Array = [start.time(b[1]), end.time(b[2])];
            C[0][0] += a[0].dot(a[0]);
            C[0][1] += a[0].dot(a[1]);
            C[1][0] = C[0][1];
            C[1][1] += a[1].dot(a[1]);
            var offset:BezierPoint = BezierPoint.operate(function(p:Number, bezier0:Number, bezier3:Number):Number
                {
                    return p - (b[0] + b[1]) * bezier0 - (b[2] + b[3]) * bezier3;
                }, _data[i], _bezier[0], _bezier[3]);
            X[0] += a[0].dot(offset);
            X[1] += a[1].dot(offset);
        }

        var alphaL:Number;
        var alphaR:Number;
        var detC:Number = C[0][0] * C[1][1] - C[1][0] * C[0][1];
        if (detC)
        {
            var detC0X:Number = C[0][0] * X[1] - C[0][1] * X[0];
            var detXC1:Number = X[0] * C[1][1] - X[1] * C[0][1];
            alphaL = detXC1 / detC;
            alphaR = detC0X / detC;
        }
        else
        {
            var c0:Number = C[0][0] + C[0][1];
            if (c0)
            {
                alphaL = X[0] / c0;
            }
            else
            {
                var c1:Number = C[1][0] + C[1][1];
                alphaL = c1 ? X[1] / c1 : 0;
            }
            alphaR = alphaL;
        }
        if (alphaL < 1e-6 || alphaR < 1e-6)
            alphaL = alphaR = _data[0].distance(_data[_len - 1]) / 3;
        _bezier[1] = start.time(alphaL).plus(_bezier[0]);
        _bezier[2] = end.time(alphaR).plus(_bezier[3]);
    }

    private function setControllBezierPoint(ei:int):void
    {
        var oi:int = 3 - ei;
        var result:BezierPoint = BezierPoint.ZERO_POINT;
        var den:Number = 0;
        for (var i:int = 0; i < _len; i++)
        {
            var b:Array = bezier(_params[i]);
            result = BezierPoint.operate(function(r:Number, b0:Number, b3:Number, bo:Number, p:Number):Number
                {
                    return r + b[ei] * (b[0] * b0 + b[3] * b3 + b[oi] * bo - p);
                }, result, _bezier[0], _bezier[3], _bezier[oi], _data[i]);
            den -= b[ei] * b[ei];
        }
        if (den)
        {
            result = result.time(1 / den);
        }
        else
        {
            result = BezierPoint.operate(function(p0:Number, p3:Number):Number
                {
                    return (oi * p0 + ei * p3) / 3;
                }, _bezier[0], _bezier[3]);
        }
        _bezier[ei] = result;
    }

    private function setParamsByLength():void
    {
        var i:int;
        _params = [0];
        for (i = 1; i < _len; i++)
            _params[i] = _params[i - 1] + _data[i].distance(_data[i - 1]);
        var total:Number = _params[_len - 1];
        for (i = 0; i < _len; i++)
            _params[i] /= total;
    }

    private function setup(points:Array, start:BezierPoint, end:BezierPoint):void
    {
        _bezier = [];
        _data = points;
        _params = [];
        _len = _data.length;
        _startTangent = start || BezierPoint.ZERO_POINT;
        _endTangent = end || BezierPoint.ZERO_POINT;
        _splitBezierPoint = 0;
    }
}
import flash.display.DisplayObject;
import flash.geom.Matrix3D;
import flash.geom.Point;
import flash.geom.Vector3D;

import org.papervision3d.cameras.Camera3D;
import org.papervision3d.objects.DisplayObject3D;

internal class WanderingTarget extends Point
{
    /** 
     * SEE http://blog.soulwire.co.uk/laboratory/flash/as3-flocking-steering-behaviors
     * SOUL WIRE
     */
    private var _matrix : Matrix3D;
    private var _maxForce : Number;
    private var _maxSpeed : Number;
    private var _distance : Number;
    private var _drawScale : Number;
    private var _maxForceSQ : Number;
    private var _maxSpeedSQ : Number;
    private var _velocity : Vector3D;
    private var _position : Vector3D;
    private var _oldPosition : Vector3D;
    private var _acceleration : Vector3D;
    private var _steeringForce : Vector3D;
    private var _screenCoords : Point;
    private var _renderData : DisplayObject;
    private var _edgeBehavior : String;
    private var _boundsRadius : Number;
    private var _boundsCentre : Vector3D = new Vector3D();
    private var _radius : Number = 10.0;
    private var _wanderTheta : Number = 0.0;
    private var _wanderRadius : Number = 16.0;
    private var _wanderDistance : Number = 60.0;
    private var _wanderStep : Number = 0.25;
    private var _lookAtTarget : Boolean = true;
    
    protected var _config : Object = {
        minForce:3.0,
        maxForce:6.0,
        minSpeed:6.0,
        maxSpeed:12.0,
        minWanderDistance:10.0,
        maxWanderDistance:100.0,
        minWanderRadius:5.0,
        maxWanderRadius:20.0,
        minWanderStep:0.1,
        maxWanderStep:0.9,
        boundsRadius:250,
        numBoids:120
    };
    
    public function WanderingTarget()
    {
        maxForce = 4;//random(_config.minForce, _config.maxForce);
        maxSpeed = 10//;random(_config.minSpeed, _config.maxSpeed);
        _wanderDistance = 25;//random(_config.minWanderDistance, _config.maxWanderDistance);
        _wanderRadius = 15;//random(_config.minWanderRadius, _config.maxWanderRadius);
        _wanderStep = 5;//random(_config.minWanderStep, _config.maxWanderStep);
        
        super();
        reset();
        //super(fov, near, far, useCulling, useProjection);
    }
    
    /**
     * Generates a random wandering force for the Boid. 
     * The results of this method can be controlled by the 
     * _wanderDistance, _wanderStep and _wanderRadius parameters
     * 
     * @param    multiplier
     * 
     * By multiplying the force generated by this behavior, 
     * more or less weight can be given to this behavior in
     * comparison to other behaviors being calculated by the 
     * Boid. To increase the weighting of this behavior, use 
     * a number above 1.0, or to decrease it use a number 
     * below 1.0
     */
    
    public function wander( multiplier : Number = 1.0 ) : void
    {
        _wanderTheta += Math.random() * _wanderStep;
        
        if ( Math.random() < 0.5 )
        {
            _wanderTheta = -_wanderTheta;
        }
        
        var pos : Vector3D = _velocity.clone();
        
        //trace(pos)
        
        pos.normalize();
        pos.scaleBy(_wanderDistance);
        pos.incrementBy(_position);
        
        var offset : Vector3D = new Vector3D();
        
        offset.x = _wanderRadius * Math.cos(_wanderTheta);
        offset.y = _wanderRadius * Math.sin(_wanderTheta);
        //offset.z = _wanderRadius * Math.tan(_wanderTheta);
        //    trace(offset)
        //trace(_wanderRadius, _wanderTheta, pos, offset)
        _steeringForce = steer(pos.add(offset));
        
        if ( multiplier != 1.0 )
        {
            _steeringForce.scaleBy(multiplier);
        }
        //    trace(_steeringForce)
        
        _acceleration.incrementBy(_steeringForce);
    }
    
    private function steer( target : Vector3D, ease : Boolean = false, easeDistance : Number = 100 ) : Vector3D
    {
        //trace(_steeringForce,target.clone());
        
        _steeringForce = target.clone();
        _steeringForce.decrementBy(_position);
        //trace(_steeringForce,target.clone());
        _distance = _steeringForce.normalize();
        //trace('ab', _distance)
        
        if ( _distance > 0.00001 )
        {
            if ( _distance < easeDistance && ease )
            {
                _steeringForce.scaleBy(_maxSpeed * ( _distance / easeDistance ));
            }
            else
            {
                _steeringForce.scaleBy(_maxSpeed);
            }
            
            _steeringForce.decrementBy(_velocity);
            
            if ( _steeringForce.lengthSquared > _maxForceSQ )
            {
                _steeringForce.normalize();
                _steeringForce.scaleBy(_maxForce);
            }
        }
        //trace(_steeringForce)
        
        return _steeringForce;
    }
    
    public function update() : void
    {
        _oldPosition.x = _position.x;
        _oldPosition.y = _position.y;
        _oldPosition.z = _position.z;
        
        _velocity.incrementBy(_acceleration);
        
        if ( _velocity.lengthSquared > _maxSpeedSQ )
        {
            _velocity.normalize();
            _velocity.scaleBy(_maxSpeed);
        }
        
        _position.incrementBy(_velocity);
        
        x = _position.x;
        y = _position.y;
        //z = _position.z;
        
        
        _acceleration.x = 0;
        _acceleration.y = 0;
        _acceleration.z = 0;
        
        if ( isNaN(_boundsRadius) )
        {
            trace( "isNaN(_boundsRadius) : " + isNaN(_boundsRadius) );
            return;
        }
        
        if( !_position.equals(_oldPosition) )
        {
            var distance : Number = Vector3D.distance(_position, _boundsCentre);
            
            if( distance > _boundsRadius + _radius )
            {
                    
                /**
                 * Move the boid to the edge of the boundary 
                 * then invert it's velocity and step it 
                 * forward back into the sphere 
                 */
                
                _position.decrementBy(_boundsCentre);
                _position.normalize();
                _position.scaleBy(_boundsRadius + _radius);
                
                _velocity.scaleBy(-1);
                _position.incrementBy(_velocity);
                _position.incrementBy(_boundsCentre);
                    
            }
        }
    }
    
    /**
     * Resets the Boid's position, velocity, acceleration and 
     * current steering force to zero
     */
    
    public function reset() : void
    {
        _velocity = new Vector3D();
        _position = new Vector3D();
        _oldPosition = new Vector3D();
        _acceleration = new Vector3D();
        _steeringForce = new Vector3D();
        _screenCoords = new Point();
    }
    
    /**
     * The maximum force available to the Boid when
     * calculating the steering force produced by 
     * the Boids steering bahaviors
     */
    
    public function get maxForce() : Number
    {
        return _maxForce;
    }
    
    public function set maxForce( value : Number ) : void
    {
        if ( value < 0 )
        {
            value = 0;
        }
        
        _maxForce = value;
        _maxForceSQ = value * value;
    }
    
    /**
     * The maximum speed the Boid can reach
     */
    
    public function get maxSpeed() : Number
    {
        return _maxSpeed;
    }
    
    public function set maxSpeed( value : Number ) : void
    {
        if ( value < 0 )
        {
            value = 0;
        }
        
        _maxSpeed = value;
        _maxSpeedSQ = value * value;
    }
    
    protected function random( min : Number, max : Number = NaN ) : Number
    {
        if ( isNaN(max) )
        {
            max = min;
            min = 0;
        }
        
        return Math.random() * ( max - min ) + min;
    }
    
    /**
     * The centrepoint of the Boids bounding sphere.
     * If the Boid travels futher than boundsRadius 
     * from this point the specified edge behavior 
     * will take affect.
     */

    public function get boundsCentre() : Vector3D
    {
        return _boundsCentre;
    }

    public function set boundsCentre( value : Vector3D ) : void
    {
        _boundsCentre = value;
    }

    /**
     * The maximum distance which this Boid can 
     * travel from it's boundsCentre before the 
     * specified edge behavior takes affect
     */

    public function get boundsRadius() : Number
    {
        return _boundsRadius;
    }

    public function set boundsRadius( value : Number ) : void
    {
        _boundsRadius = value;
    }    
    
    public function get position():Vector3D 
    {
        return _position;
    }
    
    public function set position(value:Vector3D):void 
    {
        _position = value;
    }
}