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
Subclasses
Constructors
factory Path.fromPoints(vectors) #
factory Path.fromPoints( vectors ) => new Path(vectors);
Properties
List<PathAction> actions #
List<PathAction> actions
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 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 );
}