gcanvas
Version:
A Canvas API implementation that generates Gcode
507 lines (375 loc) • 10.6 kB
JavaScript
/**
* Derived from code originally written by zz85 for three.js
* http://www.lab4games.net/zz85/blog
* Thanks zz85!
**/
module.exports = SubPath;
var Point = require('./math/point')
, ClipperLib = require('./clipper')
, Path = require('./path')
, utils = require('./utils');
function SubPath( points ) {
this.actions = [];
this.pointsCache = [];
if ( points ) {
this.fromPoints( points );
}
};
SubPath.actions = {
MOVE_TO: 'moveTo',
LINE_TO: 'lineTo',
QUADRATIC_CURVE_TO: 'quadraticCurveTo',
BEZIER_CURVE_TO: 'bezierCurveTo',
ELLIPSE: 'ellipse'
};
SubPath.prototype = {
clone: function() {
var path = new SubPath();
path.actions = this.actions.slice(0);
return path;
}
, isClosed: function() {
var fp = this.firstPoint();
var lp = this.lastPoint();
return utils.samePos(fp,lp);
}
, offset: function(delta) {
var tmp = this.toPath().offset(delta);
if(!tmp) return false;
return tmp.subPaths[0];
}
, simplify: function() {
var tmp = this.toPath().simplify();
if(!tmp) return false;
return tmp.subPaths[0];
}
, toPath: function() {
var clone = this.clone();
var path = new Path();
path.subPaths.push(clone);
path.current = path.subPaths[path.subPaths.length-1];
return path;
}
, addAction: function(action) {
this.actions.push(action);
this.pointsCache = [];
}
, firstPoint: function() {
var p = new Point(0,0);
var action = this.actions[0];
var args = action.args;
switch(action.action) {
case 'ellipse':
p = utils.arcToPoints(
args[0], args[1], args[4],
args[5], args[2]).start
break;
default:
p.x = args[args.length-2];
p.y = args[args.length-1];
break;
}
return p;
}
, lastPoint: function() {
var p = new Point(0,0);
var action = this.actions[this.
actions.length-1];
var args = action.args;
switch(action.action) {
case 'ellipse':
p = utils.arcToPoints(
args[0], args[1], args[4],
args[5], args[2]).end
break;
default:
p.x = args[args.length-2];
p.y = args[args.length-1];
break;
}
return p;
}
, fromPoints: function ( points ) {
this.moveTo( points[ 0 ].x, points[ 0 ].y );
for ( var v = 1, vlen = points.length; v < vlen; v ++ ) {
this.lineTo( points[ v ].x, points[ v ].y );
};
}
, getActionLength: function(x0,y0,i) {
var action = this.actions[i],
args = action.args;
if(action.action == 'ellipse') {
var rad = args[3];
var astart = args[4];
var aend = args[5];
return (aend-astart)*rad;
}
var x = args[args.length-2];
var y = args[args.length-1];
var xo = x - x0;
var yo = y - y0;
return Math.sqrt(xo*xo + yo*yo);
}
, getLength: function() {
var args, x1=0, y1=0, x2=0, y2=0, xo=0, yo=0, len=0;
var first = this.firstPoint();
x2 = first.x;
y2 = first.y;
var pts = this.getPoints(10000);
for(var i=1,l=pts.length; i < l; ++i) {
var p=pts[i];
x1 = x2;
y1 = y2;
x2 = p.x;
y2 = p.y;
xo = x2-x1;
yo = y2-y1;
len += Math.sqrt(xo*xo + yo*yo);
}
return len;
}
, nearestPoint: function(p1) {
var p2 = new Point()
, args
, rn
, rp
, rd = Infinity;
this.actions.forEach(function(action,n) {
args = action.args;
p2.x = args[args.length-2];
p2.y = args[args.length-1];
var d = Point.distance(p1,p2);
if(d < rd) {
rn = n;
rp = p2.clone();
rd = d;
}
});
return {
i: rn
, distance: rd
, point: rp
};
}
, pointAt: function(i) {
var p = new Point();
var action = this.actions[i];
var args = action.args;
switch(action.action) {
case 'lineTo':
p.x = args[args.length-2];
p.y = args[args.length-1];
break;
}
return p;
}
, shiftToNearest: function(x, y) {
var nearest = this.nearestPoint(new Point(x,y));
return this.shift(nearest.i);
}
, shift: function(an) {
if(an === 0) return this;
var result = new SubPath();
result.actions = this.actions.slice(an).concat(
this.actions.slice(0,an)
);
result.actions.forEach(function(a) {
a.action = SubPath.actions.LINE_TO;
});
result.lineTo.apply(result, result.actions[0].args);
return result;
}
, moveTo: function ( x, y ) {
this.addAction( { action: SubPath.actions.MOVE_TO, args: arguments } );
}
, lineTo: function ( x, y ) {
this.addAction( { action: SubPath.actions.LINE_TO, args: arguments } );
}
, quadraticCurveTo: function( aCPx, aCPy, aX, aY ) {
this.addAction( { action: SubPath.actions.QUADRATIC_CURVE_TO, args: arguments } );
}
, bezierCurveTo: function( aCP1x, aCP1y,
aCP2x, aCP2y,
aX, aY ) {
this.addAction( { action: SubPath.actions.BEZIER_CURVE_TO, args: arguments } );
}
, arc: function ( aX, aY, aRadius, aStartAngle, aEndAngle, aClockwise ) {
this.ellipse(aX, aY, aRadius, aRadius, aStartAngle, aEndAngle, aClockwise);
}
, ellipse: function ( aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise ) {
this.addAction( { action: SubPath.actions.ELLIPSE, args: arguments } );
}
, getPoints: function( divisions ) {
divisions = divisions || 40;
if(this.pointsCache[divisions]) {
return this.pointsCache[divisions];
}
var points = [];
var i, il, item, action, args;
var cpx, cpy, cpx2, cpy2, cpx1, cpy1, cpx0, cpy0,
laste, j,
t, tx, ty;
for ( i = 0, il = this.actions.length; i < il; i ++ ) {
item = this.actions[ i ];
action = item.action;
args = item.args;
switch( action ) {
case SubPath.actions.MOVE_TO:
points.push( new Point( args[ 0 ], args[ 1 ] ) );
break;
case SubPath.actions.LINE_TO:
points.push( new Point( args[ 0 ], args[ 1 ] ) );
break;
case SubPath.actions.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 = this.actions[ i - 1 ].args;
cpx0 = laste[ laste.length - 2 ];
cpy0 = laste[ laste.length - 1 ];
}
for ( j = 1; j <= divisions; j ++ ) {
t = j / divisions;
tx = b2( t, cpx0, cpx1, cpx );
ty = b2( t, cpy0, cpy1, cpy );
points.push( new Point( tx, ty ) );
}
break;
case SubPath.actions.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 = this.actions[ i - 1 ].args;
cpx0 = laste[ laste.length - 2 ];
cpy0 = laste[ laste.length - 1 ];
}
for ( j = 1; j <= divisions; j ++ ) {
t = j / divisions;
tx = b3( t, cpx0, cpx1, cpx2, cpx );
ty = b3( t, cpy0, cpy1, cpy2, cpy );
points.push( new Point( tx, ty ) );
}
break;
case SubPath.actions.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;
for ( j = 0; j <= divisions; j ++ ) {
t = j / divisions;
if(deltaAngle === -Math.PI*2) {
deltaAngle = Math.PI*2;
}
if(deltaAngle < 0) {
deltaAngle += Math.PI*2;
}
if(deltaAngle > Math.PI*2) {
deltaAngle -= Math.PI*2;
}
if ( aClockwise ) {
// sin(pi) and sin(0) are the same
// So we have to special case for full circles
if(deltaAngle === Math.PI*2) {
deltaAngle = 0;
}
angle = aEndAngle + ( 1 - t ) * ( Math.PI * 2 - deltaAngle );
} else {
angle = aStartAngle + t * deltaAngle;
}
var tx = aX + xRadius * Math.cos( angle );
var ty = aY + yRadius * Math.sin( angle );
points.push( new Point( tx, ty ) );
}
break;
} // end switch
}
if(this.closed) {
points.push( points[ 0 ] );
}
// this.pointsCache[divisions] = points;
return points;
}
, toPoly: function(scale, divisions) {
return this.getPoints(divisions).map(function(p) {
return {X: p.x*scale, Y: p.y*scale};
});
}
, fromPoly: function(poly, scale) {
scale = 1/scale;
this.moveTo(poly[0].X*scale, poly[0].Y*scale);
for(var i=1,l=poly.length; i < l; ++i) {
this.lineTo(poly[i].X*scale, poly[i].Y*scale);
}
this.close();
// todo: close properly (closePath())
// this.lineTo(poly[0].X*scale, poly[0].Y*scale);
return this;
}
, close: function() {
if(this.isClosed()) return;
var curStart = this.actions[0].args;
this.lineTo.apply(this, curStart);
}
, reverse: function() {
var result = new SubPath();
var pts = this.getPoints().reverse();
if(pts.length == 0) return result;
result.moveTo(pts[0].x, pts[0].y);
for(var i=1,l=pts.length; i < l; ++i) {
result.lineTo(pts[i].x, pts[i].y);
}
return result;
}
};
// Bezier Curves formulas obtained from
// http://en.wikipedia.org/wiki/B%C3%A9zier_curve
// Quad Bezier Functions
function b2p0 ( t, p ) {
var k = 1 - t;
return k * k * p;
}
function b2p1 ( t, p ) {
return 2 * ( 1 - t ) * t * p;
}
function b2p2 ( t, p ) {
return t * t * p;
}
function b2 ( t, p0, p1, p2 ) {
return b2p0( t, p0 ) + b2p1( t, p1 ) + b2p2( t, p2 );
}
// Cubic Bezier Functions
function b3p0 ( t, p ) {
var k = 1 - t;
return k * k * k * p;
}
function b3p1 ( t, p ) {
var k = 1 - t;
return 3 * k * k * t * p;
}
function b3p2 ( t, p ) {
var k = 1 - t;
return 3 * k * t * t * p;
}
function b3p3 ( t, p ) {
return t * t * t * p;
}
function b3 ( t, p0, p1, p2, p3 ) {
return b3p0( t, p0 ) + b3p1( t, p1 ) + b3p2( t, p2 ) + b3p3( t, p3 );
}