Dart DocumentationthreePath

Path class

class Path extends CurvePath {

 var useSpacedPoints = false;

 List _points;
 List<PathAction> actions;

	Path( [List points] ) : actions = [],  super(){
	  if (points != null) {
	    _fromPoints(points);
	  }
	}

 // Create path using straight lines to connect all points
 // - vectors: array of Vector2
 factory Path.fromPoints( vectors ) => new Path(vectors);

 _fromPoints( vectors ){
    moveTo( vectors[0].x, vectors[0].y );

    for ( var v = 1, vlen = vectors.length; v < vlen; v ++ ) {
      this.lineTo( vectors[ v ].x, vectors[ v ].y );
    }
 }

 addAction(String action, [var args]) => actions.add(new PathAction(action, args));



 // startPath() endPath()?
 moveTo( x, y ) => addAction( PathAction.MOVE_TO, [x, y] );

 lineTo( x, y ) {

 	var args = [x, y];

 	var lastargs = actions.last.args;

 	var x0 = lastargs[ lastargs.length - 2 ];
 	var y0 = lastargs[ lastargs.length - 1 ];

 	var curve = new LineCurve( new Vector2( x0, y0 ), new Vector2( x, y ) );
 	curves.add( curve );

 	addAction( PathAction.LINE_TO, args);
 }

 quadraticCurveTo( aCPx, aCPy, aX, aY ) {

 	var args = [aCPx, aCPy, aX, aY];

 	var lastargs = actions.last.args;

 	var x0 = lastargs[ lastargs.length - 2 ].toDouble();
 	var y0 = lastargs[ lastargs.length - 1 ].toDouble();

 	var curve = new QuadraticBezierCurve( new Vector2( x0, y0 ),
 												new Vector2( aCPx.toDouble(), aCPy.toDouble() ),
 												new Vector2( aX.toDouble(), aY.toDouble() ) );
 	curves.add( curve );

 	addAction( PathAction.QUADRATIC_CURVE_TO, args );
 }

 bezierCurveTo( aCP1x, aCP1y,
                aCP2x, aCP2y,
                aX, aY ) {

 	var args = [aCP1x, aCP1y,
                aCP2x, aCP2y,
                aX, aY];

 	var lastargs = actions.last.args;

 	var x0 = lastargs[ lastargs.length - 2 ].toDouble();
 	var y0 = lastargs[ lastargs.length - 1 ].toDouble();

 	var curve = new CubicBezierCurve( new Vector2( x0, y0 ),
 											new Vector2( aCP1x.toDouble(), aCP1y.toDouble() ),
 											new Vector2( aCP2x.toDouble(), aCP2y.toDouble() ),
 											new Vector2( aX.toDouble(), aY.toDouble() ) );
 	curves.add( curve );

 	addAction( PathAction.BEZIER_CURVE_TO, args );

 }

 splineThru( List<Vector2> pts) {

 	var args = [pts];
 	var lastargs = actions.last.args;

 	var x0 = lastargs[ lastargs.length - 2 ];
 	var y0 = lastargs[ lastargs.length - 1 ];
 //---
 	var npts = [ new Vector2( x0, y0 ) ];
 	npts.addAll(pts); //Array.prototype.push.apply( npts, pts );

 	var curve = new SplineCurve( npts );
 	curves.add( curve );

 	addAction( PathAction.CSPLINE_THRU, args);

 }

 // FUTURE: Change the API or follow canvas API?
 // TODO ARC ( x, y, x - radius, y - radius, startAngle, endAngle )

 arc( aX, aY, aRadius,
 								aStartAngle, aEndAngle, aClockwise ) {

   var lastargs = actions[ actions.length - 1].args;
   var x0 = lastargs[ lastargs.length - 2 ];
   var y0 = lastargs[ lastargs.length - 1 ];

   absarc(aX + x0, aY + y0, aRadius, aStartAngle, aEndAngle, aClockwise );

  }

 absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {

   absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);

  }

 ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) {

   var lastargs = actions.last.args;
   var x0 = lastargs[ lastargs.length - 2 ];
   var y0 = lastargs[ lastargs.length - 1 ];

   absellipse(aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise );

 }

 absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) {

   var args = [aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise];

   var curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise );
   curves.add( curve );

   var lastPoint = curve.getPoint(aClockwise ? 1 : 0);
   args.add(lastPoint.x);
   args.add(lastPoint.y);

   addAction(PathAction.ELLIPSE, args);

 }

 getSpacedPoints( [int divisions = 5, bool closedPath = false] ) {

 	if ( divisions == null ) divisions = 40;

 	var points = [];

 	for ( var i = 0; i < divisions; i ++ ) {

 		points.add( this.getPoint( i / divisions ) );

 		//if( !this.getPoint( i / divisions ) ) throw "DIE";

 	}

 	// if ( closedPath ) {
 	//
 	// 	points.push( points[ 0 ] );
 	//
 	// }

 	return points;

 }

 /* Return an array of vectors based on contour of the path */
 getPoints( [int divisions = null, closedPath = false] ) {

 	if (useSpacedPoints) {
 		return getSpacedPoints( divisions, closedPath );
 	}

 	if (divisions == null) divisions =  12;

 	List<Vector2> points = [];

 	var i, il, item, action, args;
 	var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
 		laste, j,
 		t, tx, ty;

 	for ( i = 0; i < actions.length; i ++ ) {

 		item = actions[ i ];

 		action = item.action;
 		args = item.args;

 		switch( action ) {

 		case PathAction.MOVE_TO:

 			points.add( new Vector2( args[ 0 ], args[ 1 ] ) );

 			break;

 		case PathAction.LINE_TO:

 			points.add( new Vector2( args[ 0 ], args[ 1 ] ) );

 			break;

 		case PathAction.QUADRATIC_CURVE_TO:

 			cpx  = args[ 2 ];
 			cpy  = args[ 3 ];

 			cpx1 = args[ 0 ];
 			cpy1 = args[ 1 ];

 			if ( points.length > 0 ) {

 				laste = points[ points.length - 1 ];

 				cpx0 = laste.x;
 				cpy0 = laste.y;

 			} else {

 				laste = actions[ i - 1 ].args;

 				cpx0 = laste[ laste.length - 2 ];
 				cpy0 = laste[ laste.length - 1 ];

 			}

 			for ( j = 1; j <= divisions; j ++ ) {

 				t = j / divisions;

 				tx = ShapeUtils.b2( t, cpx0, cpx1, cpx );
 				ty = ShapeUtils.b2( t, cpy0, cpy1, cpy );

 				points.add( new Vector2( tx, ty ) );

 		  	}

 			break;

 		case PathAction.BEZIER_CURVE_TO:

 			cpx  = args[ 4 ];
 			cpy  = args[ 5 ];

 			cpx1 = args[ 0 ];
 			cpy1 = args[ 1 ];

 			cpx2 = args[ 2 ];
 			cpy2 = args[ 3 ];

 			if ( points.length > 0 ) {

 				laste = points[ points.length - 1 ];

 				cpx0 = laste.x;
 				cpy0 = laste.y;

 			} else {

 				laste = actions[ i - 1 ].args;

 				cpx0 = laste[ laste.length - 2 ];
 				cpy0 = laste[ laste.length - 1 ];

 			}


 			for ( j = 1; j <= divisions; j ++ ) {

 				t = j / divisions;

 				tx = ShapeUtils.b3( t, cpx0, cpx1, cpx2, cpx );
 				ty = ShapeUtils.b3( t, cpy0, cpy1, cpy2, cpy );

 				points.add( new Vector2( tx, ty ) );

 			}

 			break;

 		case PathAction.CSPLINE_THRU:

 			laste = actions[ i - 1 ].args;

 			var last = new Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
 			var spts = [ last ];

 			var n = divisions * args[ 0 ].length;

 			spts.addAll( args[ 0 ] );

 			var spline = new SplineCurve( spts );

 			for ( j = 1; j <= n; j ++ ) {

 				points.add( spline.getPointAt( j / n ) ) ;

 			}

 			break;

 		case PathAction.ARC:

 			laste = actions[ i - 1 ].args;

 			var aX = args[ 0 ], aY = args[ 1 ],
 				aRadius = args[ 2 ],
 				aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
 				aClockwise = !!args[ 5 ];


 			var deltaAngle = aEndAngle - aStartAngle;
 			var angle;
 			var tdivisions = divisions * 2;

 			for ( j = 1; j <= tdivisions; j ++ ) {

 				t = j / tdivisions;

 				if ( ! aClockwise ) {

 					t = 1 - t;

 				}

 				angle = aStartAngle + t * deltaAngle;

 				tx = aX + aRadius * Math.cos( angle );
 				ty = aY + aRadius * Math.sin( angle );

 				//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);

 				points.add( new Vector2( tx, ty ) );

 			}

 			//console.log(points);

 		  break;

 		case PathAction.ELLIPSE:

       var aX = args[ 0 ], aY = args[ 1 ],
       xRadius = args[ 2 ],
       yRadius = args[ 3 ],
       aStartAngle = args[ 4 ], aEndAngle = args[ 5 ],
       aClockwise = !!args[ 6 ];


     var deltaAngle = aEndAngle - aStartAngle;
     var angle;
     var tdivisions = divisions * 2;

     for ( j = 1; j <= tdivisions; j ++ ) {

       t = j / tdivisions;

       if ( ! aClockwise ) {

         t = 1 - t;

       }

       angle = aStartAngle + t * deltaAngle;

       tx = aX + xRadius * Math.cos( angle );
       ty = aY + yRadius * Math.sin( angle );

       //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);

       points.add( new Vector2( tx, ty ) );

     }

     //console.log(points);

     break;
 		} // end switch

 	}



 	// Normalize to remove the closing point by default.
 	var lastPoint = points[ points.length - 1];
 	var EPSILON = 0.0000000001;
 	if ( (lastPoint.x - points[ 0 ].x).abs() < EPSILON &&
         (lastPoint.y - points[ 0 ].y).abs() < EPSILON) {
 	  points.removeLast();
 	}
 	if ( closedPath ) {
 		points.add( points[ 0 ] );
 	}

 	return points;

 }



 // This was used for testing purposes. Should be removed soon.
 transform ( path, segments ) {

 	var bounds = getBoundingBox();
 	var oldPts = getPoints( segments ); // getPoints getSpacedPoints

 	//console.log( path.cacheArcLengths() );
 	//path.getLengths(400);
 	//segments = 40;

 	return getWrapPoints( oldPts, path );

 }

 // Breaks path into shapes
 toShapes() {

 	var i, il, item, action, args;

 	List<Path> subPaths = [];
 	var lastPath = new Path();

 	for ( i = 0; i < actions.length; i ++ ) {

 		item = actions[ i ];

 		args = item.args;
 		action = item.action;

 		if ( action == PathAction.MOVE_TO ) {

 			if ( lastPath.actions.length != 0 ) {

 				subPaths.add( lastPath );
 				lastPath = new Path();

 			}

 		}

 		lastPath._applyAction( action, args);

 	}

 	if ( lastPath.actions.length != 0 ) {

 		subPaths.add( lastPath );

 	}

 	// console.log(subPaths);

 	if ( subPaths.length == 0 ) return [];

 	var tmpPath;
 	Shape tmpShape;
 	List<Shape> shapes = [];

 	var holesFirst = !ShapeUtils.isClockWise( subPaths[ 0 ].getPoints() );
 	// console.log("Holes first", holesFirst);

 	if ( subPaths.length == 1) {
 		tmpPath = subPaths[0];
 		tmpShape = new Shape();
 		tmpShape.actions = tmpPath.actions;
 		tmpShape.curves = tmpPath.curves;
 		shapes.add( tmpShape );
 		return shapes;
 	};

 	if ( holesFirst ) {

 		tmpShape = new Shape();

 		for ( i = 0; i < subPaths.length; i ++ ) {

 			tmpPath = subPaths[ i ];

 			if ( ShapeUtils.isClockWise( tmpPath.getPoints() ) ) {

 				tmpShape.actions = tmpPath.actions;
 				tmpShape.curves = tmpPath.curves;

 				shapes.add( tmpShape );
 				tmpShape = new Shape();

 				//console.log('cw', i);

 			} else {

 				tmpShape.holes.add( tmpPath );

 				//console.log('ccw', i);

 			}

 		}

 	} else {

 		// Shapes first

 		for ( i = 0; i < subPaths.length; i ++ ) {

 			tmpPath = subPaths[ i ];

 			if ( ShapeUtils.isClockWise( tmpPath.getPoints() ) ) {


 				if (tmpShape != null) shapes.add( tmpShape );

 				tmpShape = new Shape();
 				tmpShape.actions = tmpPath.actions;
 				tmpShape.curves = tmpPath.curves;

 			} else {

 				tmpShape.holes.add( tmpPath );

 			}

 		}

 		shapes.add( tmpShape );

 	}

 	//console.log("shape", shapes);

 	return shapes;

 }


 // TODO(nelsonsilva) - Come up with a better way to invoke the action
 _applyAction( action, args) {
   switch (action) {
     case PathAction.MOVE_TO:
       moveTo(args[0], args[1]);
       break;
     case PathAction.LINE_TO:
       lineTo(args[0], args[1]);
       break;
     case PathAction.QUADRATIC_CURVE_TO:
       quadraticCurveTo(args[0], args[1], args[2], args[3]);
       break;
     case PathAction.BEZIER_CURVE_TO:
       bezierCurveTo(args[0], args[1], args[2], args[3], args[4], args[5]);
       break;
     case PathAction.CSPLINE_THRU:
       splineThru(args[0]);
       break;
     case PathAction.ARC:
       arc(args[0], args[1], args[2], args[3], args[4], args[5]);
       break;
     case PathAction.ELLIPSE:
       ellipse(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
       break;
   }
 }
}

Extends

Curve > CurvePath > Path

Subclasses

Shape

Constructors

new Path([List points]) #

Creates a new Object instance.

Object instances have no meaningful state, and are only useful through their identity. An Object instance is equal to itself only.

docs inherited from Object
Path( [List points] ) : actions = [],  super(){
	  if (points != null) {
	    _fromPoints(points);
	  }
	}

factory Path.fromPoints(vectors) #

factory Path.fromPoints( vectors ) => new Path(vectors);

Properties

List<PathAction> actions #

List<PathAction> actions

bool autoClose #

inherited from CurvePath
bool autoClose

List cacheArcLengths #

inherited from Curve
List cacheArcLengths = null

List cacheLengths #

inherited from CurvePath
List cacheLengths = null

List curves #

inherited from CurvePath
List curves

final num length #

inherited from CurvePath
num get length => getCurveLengths().last;

bool needsUpdate #

inherited from Curve
bool needsUpdate = false

var useSpacedPoints #

var useSpacedPoints = false

Methods

dynamic absarc(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) #

absarc( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {

 absellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);

}

dynamic absellipse(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise) #

absellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) {

 var args = [aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise];

 var curve = new EllipseCurve( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise );
 curves.add( curve );

 var lastPoint = curve.getPoint(aClockwise ? 1 : 0);
 args.add(lastPoint.x);
 args.add(lastPoint.y);

 addAction(PathAction.ELLIPSE, args);

}

dynamic add(curve) #

inherited from CurvePath
add( curve ) => curves.add( curve );

dynamic addAction(String action, [args]) #

addAction(String action, [var args]) => actions.add(new PathAction(action, args));

dynamic addWrapPath(bendpath) #

inherited from CurvePath

Bend / Wrap Helper Methods

addWrapPath( bendpath ) => _bends.add( bendpath );

dynamic arc(aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise) #

arc( aX, aY, aRadius,
								aStartAngle, aEndAngle, aClockwise ) {

 var lastargs = actions[ actions.length - 1].args;
 var x0 = lastargs[ lastargs.length - 2 ];
 var y0 = lastargs[ lastargs.length - 1 ];

 absarc(aX + x0, aY + y0, aRadius, aStartAngle, aEndAngle, aClockwise );

}

dynamic bezierCurveTo(aCP1x, aCP1y, aCP2x, aCP2y, aX, aY) #

bezierCurveTo( aCP1x, aCP1y,
              aCP2x, aCP2y,
              aX, aY ) {

	var args = [aCP1x, aCP1y,
              aCP2x, aCP2y,
              aX, aY];

	var lastargs = actions.last.args;

	var x0 = lastargs[ lastargs.length - 2 ].toDouble();
	var y0 = lastargs[ lastargs.length - 1 ].toDouble();

	var curve = new CubicBezierCurve( new Vector2( x0, y0 ),
											new Vector2( aCP1x.toDouble(), aCP1y.toDouble() ),
											new Vector2( aCP2x.toDouble(), aCP2y.toDouble() ),
											new Vector2( aX.toDouble(), aY.toDouble() ) );
	curves.add( curve );

	addAction( PathAction.BEZIER_CURVE_TO, args );

}

dynamic checkConnection() #

inherited from CurvePath
checkConnection() {
		// TODO
		// If the ending of curve is not connected to the starting
		// or the next curve, then, this is not a real path
	}

dynamic closePath() #

inherited from CurvePath
closePath() {
		// TODO Test
		// and verify for vector3 (needs to implement equals)
		// Add a line curve if start and end of lines are not connected
		var startPoint = curves[0].getPoint(0);
		var endPoint = curves[curves.length-1].getPoint(1);

		if (!startPoint.equals(endPoint)) {
			this.curves.add( new LineCurve(endPoint, startPoint) );
		}

	}

dynamic createGeometry(points) #

inherited from CurvePath
createGeometry( points ) {

		var geometry = new Geometry();

		for ( var i = 0; i < points.length; i ++ ) {
		  var z = (points[i] is Vector3) ? points[ i ].z : 0.0;
			geometry.vertices.add( new Vector3( points[ i ].x, points[ i ].y, z) );
		}

		return geometry;
	}

dynamic createPointsGeometry({divisions}) #

inherited from CurvePath

Create Geometries Helpers

Generate geometry from path points (for Line or ParticleSystem objects)

createPointsGeometry( {divisions} ) {
		var pts = this.getPoints( divisions, true );
		return this.createGeometry( pts );
	}

dynamic createSpacedPointsGeometry([divisions]) #

inherited from CurvePath
createSpacedPointsGeometry( [divisions] ) {
		var pts = this.getSpacedPoints( divisions, true );
		return this.createGeometry( pts );
	}

dynamic ellipse(aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise) #

ellipse( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) {

 var lastargs = actions.last.args;
 var x0 = lastargs[ lastargs.length - 2 ];
 var y0 = lastargs[ lastargs.length - 1 ];

 absellipse(aX + x0, aY + y0, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise );

}

dynamic getBoundingBox() #

inherited from CurvePath
getBoundingBox() {

		var points = getPoints();

		var maxX, maxY, maxZ;
		var minX, minY, minZ;

		maxX = maxY = double.NEGATIVE_INFINITY;
		minX = minY = double.INFINITY;

		var p, i, sum;

		var v3 = points[0] is Vector3;

		sum = (v3) ? new Vector3.zero() : new Vector2.zero();

		int il = points.length;
		for ( i = 0; i < il; i ++ ) {

			p = points[ i ];

			if ( p.x > maxX ) { maxX = p.x;
			} else if ( p.x < minX ) minX = p.x;

			if ( p.y > maxY ) { maxY = p.y;
			} else if ( p.y < minY ) minY = p.y;

			if (v3) {
			  p = p as Vector3;
	      if ( p.z > maxZ ) { maxZ = p.z;
	      } else if ( p.z < minZ ) minZ = p.z;

	      (sum as Vector3).add( p );
	    } else {
	      (sum as Vector2).add( p );
	    }



		}

		var ret = {

			"minX": minX,
			"minY": minY,
			"maxX": maxX,
			"maxY": maxY,
			"centroid": (sum as dynamic).scale( 1.0 / il )

		};

		if (v3) {

	    ret["maxZ"] = maxZ;
	    ret["minZ"] = minZ;

	  }

	  return ret;
	}

List<num> getCurveLengths() #

inherited from CurvePath
List<num> getCurveLengths() {

		// We use cache values if curves and cache array are same length

		if ( this.cacheLengths != null && this.cacheLengths.length == this.curves.length ) {

			return this.cacheLengths;

		}

		// Get length of subsurve
		// Push sums into cached array

		var lengths = [], sums = 0;
		var i, il = this.curves.length;

		for ( i = 0; i < il; i ++ ) {

			sums += this.curves[ i ].length;
			lengths.add( sums );

		}

		this.cacheLengths = lengths;

		return lengths;

	}

List getLengths({num divisions: null}) #

inherited from Curve
List getLengths( {num divisions: null} ) {

		if (divisions == null) divisions = (_arcLengthDivisions != null) ? (_arcLengthDivisions): 200;

		if ( cacheArcLengths != null
			&& ( cacheArcLengths.length == divisions + 1 )
			&& !needsUpdate) {

			//console.log( "cached", this.cacheArcLengths );
			return cacheArcLengths;
		}

		needsUpdate = false;

		var cache = [];
		var current;
		var last = getPoint( 0.0 );
		var sum = 0;

		cache.add( 0 );

		for ( var p = 1; p <= divisions; p ++ ) {

			current = getPoint ( p / divisions );

			var distance;

			// TODO(nelsonsilva) - Must move distanceTo to IVector interface os create a new IHasDistance
			if (current is Vector3) {
			  distance = (current as Vector3).absoluteError( last as Vector3 );
			} else {
      distance = (current as Vector2).absoluteError( last as Vector2);
    }

			sum += distance;
			cache.add( sum );
			last = current;

		}

		cacheArcLengths = cache;

		return cache; // { sums: cache, sum:sum }; Sum is in the last element.
	}

dynamic getPoint(num t) #

inherited from CurvePath
getPoint( num t ) {

		var d = t * this.length;
		var curveLengths = this.getCurveLengths();
		var i = 0, diff;
		Curve curve;

		// To think about boundaries points.

		while ( i < curveLengths.length ) {

			if ( curveLengths[ i ] >= d ) {

				diff = curveLengths[ i ] - d;
				curve = this.curves[ i ];

				var u = 1 - diff / curve.length;

				return curve.getPointAt( u );

			}

			i ++;

		}

		return null;

		// loop where sum != 0, sum > d , sum+1 <d

	}

V getPointAt(u) #

inherited from Curve
V getPointAt( u ) {
		var t = getUtoTmapping( u );
		return getPoint( t );
	}

dynamic getPoints([int divisions = null, closedPath = false]) #

getPoints( [int divisions = null, closedPath = false] ) {

	if (useSpacedPoints) {
		return getSpacedPoints( divisions, closedPath );
	}

	if (divisions == null) divisions =  12;

	List<Vector2> points = [];

	var i, il, item, action, args;
	var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
		laste, j,
		t, tx, ty;

	for ( i = 0; i < actions.length; i ++ ) {

		item = actions[ i ];

		action = item.action;
		args = item.args;

		switch( action ) {

		case PathAction.MOVE_TO:

			points.add( new Vector2( args[ 0 ], args[ 1 ] ) );

			break;

		case PathAction.LINE_TO:

			points.add( new Vector2( args[ 0 ], args[ 1 ] ) );

			break;

		case PathAction.QUADRATIC_CURVE_TO:

			cpx  = args[ 2 ];
			cpy  = args[ 3 ];

			cpx1 = args[ 0 ];
			cpy1 = args[ 1 ];

			if ( points.length > 0 ) {

				laste = points[ points.length - 1 ];

				cpx0 = laste.x;
				cpy0 = laste.y;

			} else {

				laste = actions[ i - 1 ].args;

				cpx0 = laste[ laste.length - 2 ];
				cpy0 = laste[ laste.length - 1 ];

			}

			for ( j = 1; j <= divisions; j ++ ) {

				t = j / divisions;

				tx = ShapeUtils.b2( t, cpx0, cpx1, cpx );
				ty = ShapeUtils.b2( t, cpy0, cpy1, cpy );

				points.add( new Vector2( tx, ty ) );

		  	}

			break;

		case PathAction.BEZIER_CURVE_TO:

			cpx  = args[ 4 ];
			cpy  = args[ 5 ];

			cpx1 = args[ 0 ];
			cpy1 = args[ 1 ];

			cpx2 = args[ 2 ];
			cpy2 = args[ 3 ];

			if ( points.length > 0 ) {

				laste = points[ points.length - 1 ];

				cpx0 = laste.x;
				cpy0 = laste.y;

			} else {

				laste = actions[ i - 1 ].args;

				cpx0 = laste[ laste.length - 2 ];
				cpy0 = laste[ laste.length - 1 ];

			}


			for ( j = 1; j <= divisions; j ++ ) {

				t = j / divisions;

				tx = ShapeUtils.b3( t, cpx0, cpx1, cpx2, cpx );
				ty = ShapeUtils.b3( t, cpy0, cpy1, cpy2, cpy );

				points.add( new Vector2( tx, ty ) );

			}

			break;

		case PathAction.CSPLINE_THRU:

			laste = actions[ i - 1 ].args;

			var last = new Vector2( laste[ laste.length - 2 ], laste[ laste.length - 1 ] );
			var spts = [ last ];

			var n = divisions * args[ 0 ].length;

			spts.addAll( args[ 0 ] );

			var spline = new SplineCurve( spts );

			for ( j = 1; j <= n; j ++ ) {

				points.add( spline.getPointAt( j / n ) ) ;

			}

			break;

		case PathAction.ARC:

			laste = actions[ i - 1 ].args;

			var aX = args[ 0 ], aY = args[ 1 ],
				aRadius = args[ 2 ],
				aStartAngle = args[ 3 ], aEndAngle = args[ 4 ],
				aClockwise = !!args[ 5 ];


			var deltaAngle = aEndAngle - aStartAngle;
			var angle;
			var tdivisions = divisions * 2;

			for ( j = 1; j <= tdivisions; j ++ ) {

				t = j / tdivisions;

				if ( ! aClockwise ) {

					t = 1 - t;

				}

				angle = aStartAngle + t * deltaAngle;

				tx = aX + aRadius * Math.cos( angle );
				ty = aY + aRadius * Math.sin( angle );

				//console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);

				points.add( new Vector2( tx, ty ) );

			}

			//console.log(points);

		  break;

		case PathAction.ELLIPSE:

     var aX = args[ 0 ], aY = args[ 1 ],
     xRadius = args[ 2 ],
     yRadius = args[ 3 ],
     aStartAngle = args[ 4 ], aEndAngle = args[ 5 ],
     aClockwise = !!args[ 6 ];


   var deltaAngle = aEndAngle - aStartAngle;
   var angle;
   var tdivisions = divisions * 2;

   for ( j = 1; j <= tdivisions; j ++ ) {

     t = j / tdivisions;

     if ( ! aClockwise ) {

       t = 1 - t;

     }

     angle = aStartAngle + t * deltaAngle;

     tx = aX + xRadius * Math.cos( angle );
     ty = aY + yRadius * Math.sin( angle );

     //console.log('t', t, 'angle', angle, 'tx', tx, 'ty', ty);

     points.add( new Vector2( tx, ty ) );

   }

   //console.log(points);

   break;
		} // end switch

	}



	// Normalize to remove the closing point by default.
	var lastPoint = points[ points.length - 1];
	var EPSILON = 0.0000000001;
	if ( (lastPoint.x - points[ 0 ].x).abs() < EPSILON &&
       (lastPoint.y - points[ 0 ].y).abs() < EPSILON) {
	  points.removeLast();
	}
	if ( closedPath ) {
		points.add( points[ 0 ] );
	}

	return points;

}

dynamic getSpacedPoints([int divisions = 5, bool closedPath = false]) #

getSpacedPoints( [int divisions = 5, bool closedPath = false] ) {

	if ( divisions == null ) divisions = 40;

	var points = [];

	for ( var i = 0; i < divisions; i ++ ) {

		points.add( this.getPoint( i / divisions ) );

		//if( !this.getPoint( i / divisions ) ) throw "DIE";

	}

	// if ( closedPath ) {
	//
	// 	points.push( points[ 0 ] );
	//
	// }

	return points;

}

V getTangent(t) #

inherited from Curve
V getTangent( t ) {

		var delta = 0.0001;
		var t1 = t - delta;
		var t2 = t + delta;

		// Capping in case of danger

		if ( t1 < 0 ) t1 = 0;
		if ( t2 > 1 ) t2 = 1;

		var pt1 = getPoint( t1 );
		var pt2 = getPoint( t2 );

		var vec = pt2 - pt1;
		return vec.normalize();
	}

V getTangentAt(u) #

inherited from Curve
V getTangentAt( u ) {
		var t = getUtoTmapping( u );
		return getTangent( t );
	}

dynamic getTransformedPoints(segments, {List bends: null}) #

inherited from CurvePath
getTransformedPoints( segments, {List bends: null} ) {

		var oldPts = this.getPoints( segments ); // getPoints getSpacedPoints
		var i, il;

		if (bends == null) {
			bends = _bends;
		}

		for ( i = 0; i < bends.length; i ++ ) {
			oldPts = this.getWrapPoints( oldPts, bends[ i ] );
		}

		return oldPts;

	}

dynamic getTransformedSpacedPoints([num segments, List bends = null]) #

inherited from CurvePath
getTransformedSpacedPoints( [num segments, List bends = null] ) {

		var oldPts = getSpacedPoints( segments );

		var i, il;

		if (bends == null) {
			bends = _bends;
		}

		for ( i = 0; i < bends.length; i ++ ) {
			oldPts = this.getWrapPoints( oldPts, bends[ i ] );
		}

		return oldPts;
	}

List<V> getUPoints([List uList, closedPath = false]) #

inherited from Curve
List<V> getUPoints( [List uList , closedPath = false] ) {
 var pts = [];

 for ( var u in uList ) {
   pts.add( this.getPointAt( u ) );
 }

 return pts;
}

dynamic getUtoTmapping(u, {distance: null}) #

inherited from Curve
getUtoTmapping( u, {distance: null} ) {

		var arcLengths = getLengths();

		int i = 0, il = arcLengths.length;

		var targetArcLength; // The targeted u distance value to get

		if (distance != null) {
			targetArcLength = distance;
		} else {
			targetArcLength = u * arcLengths[ il - 1 ];
		}

		//var time = Date.now();

		// binary search for the index with largest value smaller than target u distance

		var low = 0, high = il - 1, comparison;

		while ( low <= high ) {

			i = ( low + ( high - low ) / 2 ).floor().toInt(); // less likely to overflow, though probably not issue here, JS doesn't really have integers, all numbers are floats

			comparison = arcLengths[ i ] - targetArcLength;

			if ( comparison < 0 ) {

				low = i + 1;
				continue;

			} else if ( comparison > 0 ) {

				high = i - 1;
				continue;

			} else {

				high = i;
				break;

				// DONE

			}

		}

		i = high;

		//console.log('b' , i, low, high, Date.now()- time);

		if ( arcLengths[ i ] == targetArcLength ) {

			var t = i / ( il - 1 );
			return t;

		}

		// we could get finer grain at lengths, or use simple interpolatation between two points

		var lengthBefore = arcLengths[ i ];
	    var lengthAfter = arcLengths[ i + 1 ];

	    var segmentLength = lengthAfter - lengthBefore;

	    // determine where we are between the 'before' and 'after' points

	    var segmentFraction = ( targetArcLength - lengthBefore ) / segmentLength;

	    // add that fractional amount to t

	    var t = ( i + segmentFraction ) / ( il -1 );

		return t;
	}

dynamic getWrapPoints(oldPts, path) #

inherited from CurvePath
getWrapPoints( oldPts, path ) {

		var bounds = getBoundingBox();

		var i, il, p, oldX, oldY, xNorm;

		for ( i = 0; i < oldPts.length; i ++ ) {

			p = oldPts[ i ];

			oldX = p.x;
			oldY = p.y;

			xNorm = oldX / bounds.maxX;

			// If using actual distance, for length > path, requires line extrusions
			//xNorm = path.getUtoTmapping(xNorm, oldX); // 3 styles. 1) wrap stretched. 2) wrap stretch by arc length 3) warp by actual distance

			xNorm = path.getUtoTmapping( xNorm, oldX );

			// check for out of bounds?

			var pathPt = path.getPoint( xNorm );
			var normal = path.getNormalVector( xNorm ).scale( oldY );

			p.x = pathPt.x + normal.x;
			p.y = pathPt.y + normal.y;

		}

		return oldPts;

	}

dynamic lineTo(x, y) #

lineTo( x, y ) {

	var args = [x, y];

	var lastargs = actions.last.args;

	var x0 = lastargs[ lastargs.length - 2 ];
	var y0 = lastargs[ lastargs.length - 1 ];

	var curve = new LineCurve( new Vector2( x0, y0 ), new Vector2( x, y ) );
	curves.add( curve );

	addAction( PathAction.LINE_TO, args);
}

dynamic moveTo(x, y) #

moveTo( x, y ) => addAction( PathAction.MOVE_TO, [x, y] );

dynamic quadraticCurveTo(aCPx, aCPy, aX, aY) #

quadraticCurveTo( aCPx, aCPy, aX, aY ) {

	var args = [aCPx, aCPy, aX, aY];

	var lastargs = actions.last.args;

	var x0 = lastargs[ lastargs.length - 2 ].toDouble();
	var y0 = lastargs[ lastargs.length - 1 ].toDouble();

	var curve = new QuadraticBezierCurve( new Vector2( x0, y0 ),
												new Vector2( aCPx.toDouble(), aCPy.toDouble() ),
												new Vector2( aX.toDouble(), aY.toDouble() ) );
	curves.add( curve );

	addAction( PathAction.QUADRATIC_CURVE_TO, args );
}

dynamic splineThru(List<Vector2> pts) #

splineThru( List<Vector2> pts) {

	var args = [pts];
	var lastargs = actions.last.args;

	var x0 = lastargs[ lastargs.length - 2 ];
	var y0 = lastargs[ lastargs.length - 1 ];
//---
	var npts = [ new Vector2( x0, y0 ) ];
	npts.addAll(pts); //Array.prototype.push.apply( npts, pts );

	var curve = new SplineCurve( npts );
	curves.add( curve );

	addAction( PathAction.CSPLINE_THRU, args);

}

dynamic toShapes() #

toShapes() {

	var i, il, item, action, args;

	List<Path> subPaths = [];
	var lastPath = new Path();

	for ( i = 0; i < actions.length; i ++ ) {

		item = actions[ i ];

		args = item.args;
		action = item.action;

		if ( action == PathAction.MOVE_TO ) {

			if ( lastPath.actions.length != 0 ) {

				subPaths.add( lastPath );
				lastPath = new Path();

			}

		}

		lastPath._applyAction( action, args);

	}

	if ( lastPath.actions.length != 0 ) {

		subPaths.add( lastPath );

	}

	// console.log(subPaths);

	if ( subPaths.length == 0 ) return [];

	var tmpPath;
	Shape tmpShape;
	List<Shape> shapes = [];

	var holesFirst = !ShapeUtils.isClockWise( subPaths[ 0 ].getPoints() );
	// console.log("Holes first", holesFirst);

	if ( subPaths.length == 1) {
		tmpPath = subPaths[0];
		tmpShape = new Shape();
		tmpShape.actions = tmpPath.actions;
		tmpShape.curves = tmpPath.curves;
		shapes.add( tmpShape );
		return shapes;
	};

	if ( holesFirst ) {

		tmpShape = new Shape();

		for ( i = 0; i < subPaths.length; i ++ ) {

			tmpPath = subPaths[ i ];

			if ( ShapeUtils.isClockWise( tmpPath.getPoints() ) ) {

				tmpShape.actions = tmpPath.actions;
				tmpShape.curves = tmpPath.curves;

				shapes.add( tmpShape );
				tmpShape = new Shape();

				//console.log('cw', i);

			} else {

				tmpShape.holes.add( tmpPath );

				//console.log('ccw', i);

			}

		}

	} else {

		// Shapes first

		for ( i = 0; i < subPaths.length; i ++ ) {

			tmpPath = subPaths[ i ];

			if ( ShapeUtils.isClockWise( tmpPath.getPoints() ) ) {


				if (tmpShape != null) shapes.add( tmpShape );

				tmpShape = new Shape();
				tmpShape.actions = tmpPath.actions;
				tmpShape.curves = tmpPath.curves;

			} else {

				tmpShape.holes.add( tmpPath );

			}

		}

		shapes.add( tmpShape );

	}

	//console.log("shape", shapes);

	return shapes;

}

dynamic transform(path, segments) #

transform ( path, segments ) {

	var bounds = getBoundingBox();
	var oldPts = getPoints( segments ); // getPoints getSpacedPoints

	//console.log( path.cacheArcLengths() );
	//path.getLengths(400);
	//segments = 40;

	return getWrapPoints( oldPts, path );

}

dynamic updateArcLengths() #

inherited from Curve
updateArcLengths() {
		needsUpdate = true;
		getLengths();
	}