mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2024-12-29 09:22:22 -05:00
Seperate Path definition into two parts, one that requires scoped private functions and values, and one that does not, to keep related things closer together and save indentation space.
This commit is contained in:
parent
19121c33b2
commit
5850ef3cfc
1 changed files with 386 additions and 384 deletions
770
src/path/Path.js
770
src/path/Path.js
|
@ -1,14 +1,377 @@
|
|||
Path = PathItem.extend(new function() {
|
||||
var styleNames = {
|
||||
fillColor: 'fillStyle',
|
||||
strokeColor: 'strokeStyle',
|
||||
strokeWidth: 'lineWidth',
|
||||
strokeJoin: 'lineJoin',
|
||||
strokeCap: 'lineCap',
|
||||
miterLimit: 'miterLimit'
|
||||
};
|
||||
Path = PathItem.extend({
|
||||
beans: true,
|
||||
|
||||
initialize: function(/* segments */) {
|
||||
this.base();
|
||||
this.closed = false;
|
||||
this._segments = [];
|
||||
// Support both passing of segments as array or arguments
|
||||
// If it is an array, it can also be a description of a point, so
|
||||
// check its first entry for object as well
|
||||
var segments = arguments[0];
|
||||
if (!segments || !Array.isArray(segments)
|
||||
|| typeof segments[0] != 'object')
|
||||
segments = arguments;
|
||||
for (var i = 0, l = segments.length; i < l; i++)
|
||||
this.addSegment(new Segment(segments[i]));
|
||||
},
|
||||
|
||||
/**
|
||||
* The segments contained within the path.
|
||||
*/
|
||||
getSegments: function() {
|
||||
return this._segments;
|
||||
},
|
||||
|
||||
setSegments: function(segments) {
|
||||
this._segments = segments;
|
||||
},
|
||||
|
||||
/**
|
||||
* The bounding rectangle of the item excluding stroke width.
|
||||
*/
|
||||
getBounds: function() {
|
||||
// Code ported from:
|
||||
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
|
||||
var segments = this._segments;
|
||||
var first = segments[0];
|
||||
if (!first)
|
||||
return null;
|
||||
var p0 = first.point, prev = first;
|
||||
var min = {
|
||||
x: p0.x,
|
||||
y: p0.y
|
||||
};
|
||||
var max = {
|
||||
x: p0.x,
|
||||
y: p0.y
|
||||
}
|
||||
var coords = ['x', 'y'];
|
||||
function processSegment(segment) {
|
||||
var p1 = p0.add(prev.handleOut);
|
||||
var p3 = segment.point;
|
||||
var p2 = p3.add(segment.handleIn);
|
||||
for (var i = 0; i < 2; i++) {
|
||||
var coord = coords[i];
|
||||
var v0 = p0[coord], v1 = p1[coord],
|
||||
v2 = p2[coord], v3 = p3[coord];
|
||||
|
||||
function bounds(value) {
|
||||
if (value < min[coord]) {
|
||||
min[coord] = value;
|
||||
} else if (value > max[coord]) {
|
||||
max[coord] = value;
|
||||
}
|
||||
}
|
||||
bounds(v3);
|
||||
|
||||
function f(t) {
|
||||
var omt = 1 - t;
|
||||
return omt * omt * omt * v0
|
||||
+ 3 * omt * omt * t * v1
|
||||
+ 3 * omt * t * t * v2
|
||||
+ t * t * t * v3;
|
||||
}
|
||||
|
||||
var b = 6 * v0 - 12 * v1 + 6 * v2;
|
||||
var a = -3 * v0 + 9 * v1 - 9 * v2 + 3 * v3;
|
||||
var c = 3 * v1 - 3 * v0;
|
||||
|
||||
if (a == 0) {
|
||||
if (b == 0)
|
||||
continue;
|
||||
var t = -c / b;
|
||||
if (0 < t && t < 1)
|
||||
bounds(f(t));
|
||||
continue;
|
||||
}
|
||||
|
||||
var b2ac = b * b - 4 * c * a;
|
||||
if (b2ac < 0)
|
||||
continue;
|
||||
var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
|
||||
if (0 < t1 && t1 < 1)
|
||||
bounds(f(t1));
|
||||
var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
|
||||
if (0 < t2 && t2 < 1)
|
||||
bounds(f(t2));
|
||||
}
|
||||
p0 = p3;
|
||||
prev = segment;
|
||||
}
|
||||
for (var i = 1, l = segments.length; i < l; i++)
|
||||
processSegment(segments[i]);
|
||||
if (this.closed)
|
||||
processSegment(first);
|
||||
return new Rectangle(min.x, min.y, max.x - min.x , max.y - min.y);
|
||||
},
|
||||
|
||||
transformContent: function(matrix, flags) {
|
||||
for (var i = 0, l = this._segments.length; i < l; i++) {
|
||||
var segment = this._segments[i];
|
||||
// We need to convert handles to absolute coordinates in order
|
||||
// to transform them.
|
||||
// TODO: Is transformation even required if they are [0, 0]?
|
||||
// TODO: Can we optimise this by using the matrix.transform()
|
||||
// version that takes arrays as in and output values, and just
|
||||
// modifying points rather than producing new ones? This would
|
||||
// consume less memory for sure.
|
||||
var point = segment.point;
|
||||
var handleIn = segment.handleIn.add(point);
|
||||
var handleOut = segment.handleOut.add(point);
|
||||
point = matrix.transform(point);
|
||||
segment.point = point;
|
||||
// Convert handles back to relative values after transformation
|
||||
segment.handleIn = matrix.transform(handleIn).subtract(point);
|
||||
segment.handleOut = matrix.transform(handleOut).subtract(point);
|
||||
}
|
||||
},
|
||||
|
||||
addSegment: function(segment) {
|
||||
segment.path = this;
|
||||
this._segments.push(segment);
|
||||
},
|
||||
|
||||
add: function() {
|
||||
var segment = Segment.read(arguments);
|
||||
if (segment)
|
||||
this.addSegment(segment);
|
||||
},
|
||||
|
||||
insert: function(index, segment) {
|
||||
this._segments.splice(index, 0, new Segment(segment));
|
||||
},
|
||||
|
||||
/**
|
||||
* PostScript-style drawing commands
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper method that returns the current segment and checks if we need to
|
||||
* execute a moveTo() command first.
|
||||
*/
|
||||
getCurrentSegment: function() {
|
||||
if (this._segments.length == 0)
|
||||
throw('Use a moveTo() command first');
|
||||
return this._segments[this._segments.length - 1];
|
||||
},
|
||||
|
||||
moveTo: function() {
|
||||
var segment = Segment.read(arguments);
|
||||
if (segment && !this._segments.length)
|
||||
this.addSegment(segment);
|
||||
},
|
||||
|
||||
lineTo: function() {
|
||||
var segment = Segment.read(arguments);
|
||||
if (segment && this._segments.length)
|
||||
this.addSegment(segment);
|
||||
},
|
||||
|
||||
/**
|
||||
/**
|
||||
* Adds a cubic bezier curve to the path, defined by two handles and a to
|
||||
* point.
|
||||
*/
|
||||
cubicCurveTo: function(handle1, handle2, to) {
|
||||
// First modify the current segment:
|
||||
var current = this.currentSegment;
|
||||
// Convert to relative values:
|
||||
current.handleOut = new Point(
|
||||
handle1.x - current.point.x,
|
||||
handle1.y - current.point.y);
|
||||
// And add the new segment, with handleIn set to c2
|
||||
this.addSegment(
|
||||
new Segment(to, handle2.subtract(to), new Point())
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a quadratic bezier curve to the path, defined by a handle and a to
|
||||
* point.
|
||||
*/
|
||||
quadraticCurveTo: function(handle, to) {
|
||||
// This is exact:
|
||||
// If we have the three quad points: A E D,
|
||||
// and the cubic is A B C D,
|
||||
// B = E + 1/3 (A - E)
|
||||
// C = E + 1/3 (D - E)
|
||||
var current = this.currentSegment;
|
||||
var x1 = current.point.x;
|
||||
var y1 = current.point.y;
|
||||
this.cubicCurveTo(
|
||||
handle.add(current.point.subtract(handle).multiply(1/3)),
|
||||
handle.add(to.subtract(handle).multiply(1/3)),
|
||||
to
|
||||
);
|
||||
},
|
||||
|
||||
curveTo: function(through, to, parameter) {
|
||||
through = new Point(through);
|
||||
to = new Point(to);
|
||||
if (parameter == null)
|
||||
parameter = 0.5;
|
||||
var current = this.currentSegment.point;
|
||||
// handle = (through - (1 - t)^2 * current - t^2 * to) / (2 * (1 - t) * t)
|
||||
var t1 = 1 - parameter;
|
||||
var handle = through.subtract(
|
||||
current.multiply(t1 * t1)).subtract(
|
||||
to.multiply(parameter * parameter)).divide(
|
||||
2.0 * parameter * t1);
|
||||
if (handle.isNaN())
|
||||
throw new Error(
|
||||
"Cannot put a curve through points with parameter="
|
||||
+ parameter);
|
||||
this.quadraticCurveTo(handle, to);
|
||||
},
|
||||
|
||||
arcTo: function(to, clockwise) {
|
||||
var through, to;
|
||||
// Get the start point:
|
||||
var current = this.currentSegment;
|
||||
if (arguments[1] && typeof arguments[1] != 'boolean') {
|
||||
through = new Point(arguments[0]);
|
||||
to = new Point(arguments[1]);
|
||||
} else {
|
||||
if (clockwise === null)
|
||||
clockwise = true;
|
||||
var middle = current.point.add(to).divide(2);
|
||||
var step = middle.subtract(current.point);
|
||||
through = clockwise
|
||||
? middle.subtract(-step.y, step.x)
|
||||
: middle.add(-step.y, step.x);
|
||||
}
|
||||
|
||||
var x1 = current.point.x, x2 = through.x, x3 = to.x;
|
||||
var y1 = current.point.y, y2 = through.y, y3 = to.y;
|
||||
|
||||
var f = x3 * x3 - x3 * x2 - x1 * x3 + x1 * x2 + y3 * y3 - y3 * y2
|
||||
- y1 * y3 + y1 * y2;
|
||||
var g = x3 * y1 - x3 * y2 + x1 * y2 - x1 * y3 + x2 * y3 - x2 * y1;
|
||||
var m = g == 0 ? 0 : f / g;
|
||||
|
||||
var c = (m * y2) - x2 - x1 - (m * y1);
|
||||
var d = (m * x1) - y1 - y2 - (x2 * m);
|
||||
var e = (x1 * x2) + (y1 * y2) - (m * x1 * y2) + (m * x2 * y1);
|
||||
|
||||
var centerX = -c / 2;
|
||||
var centerY = -d / 2;
|
||||
var radius = Math.sqrt(centerX * centerX + centerY * centerY - e);
|
||||
|
||||
// Note: reversing the Y equations negates the angle to adjust
|
||||
// for the upside down coordinate system.
|
||||
var angle = Math.atan2(centerY - y1, x1 - centerX);
|
||||
var middle = Math.atan2(centerY - y2, x2 - centerX);
|
||||
var extent = Math.atan2(centerY - y3, x3 - centerX);
|
||||
|
||||
var diff = middle - angle;
|
||||
if (diff < -Math.PI)
|
||||
diff += Math.PI * 2;
|
||||
else if (diff > Math.PI)
|
||||
diff -= Math.PI * 2;
|
||||
|
||||
extent -= angle;
|
||||
if (extent <= 0.0)
|
||||
extent += Math.PI * 2;
|
||||
|
||||
if (diff < 0) extent = Math.PI * 2 - extent;
|
||||
else extent = -extent;
|
||||
angle = -angle;
|
||||
|
||||
var ext = Math.abs(extent);
|
||||
var arcSegs;
|
||||
if (ext >= 2 * Math.PI) arcSegs = 4;
|
||||
else arcSegs = Math.ceil(ext * 2 / Math.PI);
|
||||
|
||||
var inc = extent;
|
||||
if (inc > 2 * Math.PI) inc = 2 * Math.PI;
|
||||
else if (inc < -2 * Math.PI) inc = -2 * Math.PI;
|
||||
inc /= arcSegs;
|
||||
|
||||
var halfInc = inc / 2;
|
||||
var z = 4 / 3 * Math.sin(halfInc) / (1 + Math.cos(halfInc));
|
||||
|
||||
for (var i = 0; i <= arcSegs; i++) {
|
||||
var relx = Math.cos(angle);
|
||||
var rely = Math.sin(angle);
|
||||
var pt = new Point(centerX + relx * radius,
|
||||
centerY + rely * radius);
|
||||
var out;
|
||||
if (i == arcSegs) out = null;
|
||||
else out = new Point(centerX + (relx - z * rely) * radius - pt.x,
|
||||
centerY + (rely + z * relx) * radius - pt.y);
|
||||
if (i == 0) {
|
||||
// Modify startSegment
|
||||
current.handleOut = out;
|
||||
} else {
|
||||
// Add new Segment
|
||||
var inPoint = new Point(
|
||||
centerX + (relx + z * rely) * radius - pt.x,
|
||||
centerY + (rely - z * relx) * radius - pt.y);
|
||||
this.addSegment(new Segment(pt, inPoint, out));
|
||||
}
|
||||
angle += inc;
|
||||
}
|
||||
},
|
||||
|
||||
lineBy: function() {
|
||||
var vector = Point.read(arguments);
|
||||
if (vector) {
|
||||
var current = this.currentSegment;
|
||||
this.lineTo(current.point.add(vector));
|
||||
}
|
||||
},
|
||||
|
||||
curveBy: function(throughVector, toVector, parameter) {
|
||||
throughVector = Point.read(throughVector);
|
||||
toVector = Point.read(toVector);
|
||||
var current = this.currentSegment.point;
|
||||
this.curveTo(current.add(throughVector), current.add(toVector), parameter);
|
||||
},
|
||||
|
||||
arcBy: function(throughVector, toVector) {
|
||||
throughVector = Point.read(throughVector);
|
||||
toVector = Point.read(toVector);
|
||||
var current = this.currentSegment.point;
|
||||
this.arcBy(current.add(throughVector), current.add(toVector));
|
||||
},
|
||||
|
||||
draw: function(ctx, compound) {
|
||||
if (!this.visible) return;
|
||||
if(!compound)
|
||||
ctx.beginPath();
|
||||
var cp1;
|
||||
for (var i = 0, l = this._segments.length; i < l; i++) {
|
||||
var segment = this._segments[i];
|
||||
var point = segment.point;
|
||||
var handleIn = segment.handleIn.add(point);
|
||||
var handleOut = segment.handleOut.add(point);
|
||||
if (i == 0) {
|
||||
ctx.moveTo(point.x, point.y);
|
||||
} else {
|
||||
ctx.bezierCurveTo(cp1.x, cp1.y, handleIn.x, handleIn.y,
|
||||
point.x, point.y);
|
||||
}
|
||||
cp1 = handleOut;
|
||||
}
|
||||
if (this.closed && this._segments.length > 1) {
|
||||
var segment = this._segments[0];
|
||||
var point = segment.point;
|
||||
var handleIn = segment.handleIn.add(point);
|
||||
ctx.bezierCurveTo(cp1.x, cp1.y, handleIn.x, handleIn.y,
|
||||
point.x, point.y);
|
||||
ctx.closePath();
|
||||
}
|
||||
if(!compound) {
|
||||
this.setCtxStyles(ctx);
|
||||
if (this.fillColor) ctx.fill();
|
||||
if (this.strokeColor) ctx.stroke();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Now inject methods that require scoped private functions and values.
|
||||
Path.inject(new function() {
|
||||
/**
|
||||
* Solves a tri-diagonal system for one of coordinates (x or y) of first
|
||||
* bezier control points.
|
||||
*
|
||||
|
@ -31,336 +394,22 @@ Path = PathItem.extend(new function() {
|
|||
for (var i = 1; i < n; i++) {
|
||||
x[n - i - 1] -= tmp[n - i] * x[n - i];
|
||||
}
|
||||
|
||||
return x;
|
||||
};
|
||||
|
||||
var styleNames = {
|
||||
fillColor: 'fillStyle',
|
||||
strokeColor: 'strokeStyle',
|
||||
strokeWidth: 'lineWidth',
|
||||
strokeJoin: 'lineJoin',
|
||||
strokeCap: 'lineCap',
|
||||
miterLimit: 'miterLimit'
|
||||
};
|
||||
|
||||
return {
|
||||
beans: true,
|
||||
|
||||
initialize: function(/* segments */) {
|
||||
this.base();
|
||||
this.closed = false;
|
||||
this._segments = [];
|
||||
// Support both passing of segments as array or arguments
|
||||
// If it is an array, it can also be a description of a point, so
|
||||
// check its first entry for object as well
|
||||
var segments = arguments[0];
|
||||
if (!segments || !Array.isArray(segments)
|
||||
|| typeof segments[0] != 'object')
|
||||
segments = arguments;
|
||||
for (var i = 0, l = segments.length; i < l; i++)
|
||||
this.addSegment(new Segment(segments[i]));
|
||||
},
|
||||
|
||||
/**
|
||||
* The segments contained within the path.
|
||||
*/
|
||||
getSegments: function() {
|
||||
return this._segments;
|
||||
},
|
||||
|
||||
setSegments: function(segments) {
|
||||
this._segments = segments;
|
||||
},
|
||||
|
||||
/*
|
||||
* The bounding rectangle of the item excluding stroke width.
|
||||
*/
|
||||
getBounds: function() {
|
||||
// Code ported from:
|
||||
// http://blog.hackers-cafe.net/2009/06/how-to-calculate-bezier-curves-bounding.html
|
||||
var segments = this._segments;
|
||||
var first = segments[0];
|
||||
if (!first)
|
||||
return null;
|
||||
var p0 = first.point, prev = first;
|
||||
var min = {
|
||||
x: p0.x,
|
||||
y: p0.y
|
||||
};
|
||||
var max = {
|
||||
x: p0.x,
|
||||
y: p0.y
|
||||
}
|
||||
var coords = ['x', 'y'];
|
||||
function processSegment(segment) {
|
||||
var p1 = p0.add(prev.handleOut);
|
||||
var p3 = segment.point;
|
||||
var p2 = p3.add(segment.handleIn);
|
||||
for (var i = 0; i < 2; i++) {
|
||||
var coord = coords[i];
|
||||
var v0 = p0[coord], v1 = p1[coord],
|
||||
v2 = p2[coord], v3 = p3[coord];
|
||||
|
||||
function bounds(value) {
|
||||
if (value < min[coord]) {
|
||||
min[coord] = value;
|
||||
} else if (value > max[coord]) {
|
||||
max[coord] = value;
|
||||
}
|
||||
}
|
||||
bounds(v3);
|
||||
|
||||
function f(t) {
|
||||
var omt = 1 - t;
|
||||
return omt * omt * omt * v0
|
||||
+ 3 * omt * omt * t * v1
|
||||
+ 3 * omt * t * t * v2
|
||||
+ t * t * t * v3;
|
||||
}
|
||||
|
||||
var b = 6 * v0 - 12 * v1 + 6 * v2;
|
||||
var a = -3 * v0 + 9 * v1 - 9 * v2 + 3 * v3;
|
||||
var c = 3 * v1 - 3 * v0;
|
||||
|
||||
if (a == 0) {
|
||||
if (b == 0)
|
||||
continue;
|
||||
var t = -c / b;
|
||||
if (0 < t && t < 1)
|
||||
bounds(f(t));
|
||||
continue;
|
||||
}
|
||||
|
||||
var b2ac = b * b - 4 * c * a;
|
||||
if (b2ac < 0)
|
||||
continue;
|
||||
var t1 = (-b + Math.sqrt(b2ac)) / (2 * a);
|
||||
if (0 < t1 && t1 < 1)
|
||||
bounds(f(t1));
|
||||
var t2 = (-b - Math.sqrt(b2ac)) / (2 * a);
|
||||
if (0 < t2 && t2 < 1)
|
||||
bounds(f(t2));
|
||||
}
|
||||
p0 = p3;
|
||||
prev = segment;
|
||||
}
|
||||
for (var i = 1, l = segments.length; i < l; i++)
|
||||
processSegment(segments[i]);
|
||||
if (this.closed)
|
||||
processSegment(first);
|
||||
return new Rectangle(min.x, min.y, max.x - min.x , max.y - min.y);
|
||||
},
|
||||
|
||||
transformContent: function(matrix, flags) {
|
||||
for (var i = 0, l = this._segments.length; i < l; i++) {
|
||||
var segment = this._segments[i];
|
||||
// We need to convert handles to absolute coordinates in order
|
||||
// to transform them.
|
||||
// TODO: Is transformation even required if they are [0, 0]?
|
||||
// TODO: Can we optimise this by using the matrix.transform()
|
||||
// version that takes arrays as in and output values, and just
|
||||
// modifying points rather than producing new ones? This would
|
||||
// consume less memory for sure.
|
||||
var point = segment.point;
|
||||
var handleIn = segment.handleIn.add(point);
|
||||
var handleOut = segment.handleOut.add(point);
|
||||
point = matrix.transform(point);
|
||||
segment.point = point;
|
||||
// Convert handles back to relative values after transformation
|
||||
segment.handleIn = matrix.transform(handleIn).subtract(point);
|
||||
segment.handleOut = matrix.transform(handleOut).subtract(point);
|
||||
}
|
||||
},
|
||||
|
||||
addSegment: function(segment) {
|
||||
segment.path = this;
|
||||
this._segments.push(segment);
|
||||
},
|
||||
|
||||
add: function() {
|
||||
var segment = Segment.read(arguments);
|
||||
if (segment)
|
||||
this.addSegment(segment);
|
||||
},
|
||||
|
||||
insert: function(index, segment) {
|
||||
this._segments.splice(index, 0, new Segment(segment));
|
||||
},
|
||||
|
||||
/**
|
||||
* PostScript-style drawing commands
|
||||
*/
|
||||
|
||||
/**
|
||||
* Helper method that returns the current segment and checks if we need to
|
||||
* execute a moveTo() command first.
|
||||
*/
|
||||
getCurrentSegment: function() {
|
||||
if (this._segments.length == 0)
|
||||
throw('Use a moveTo() command first');
|
||||
return this._segments[this._segments.length - 1];
|
||||
},
|
||||
|
||||
moveTo: function() {
|
||||
var segment = Segment.read(arguments);
|
||||
if (segment && !this._segments.length)
|
||||
this.addSegment(segment);
|
||||
},
|
||||
|
||||
lineTo: function() {
|
||||
var segment = Segment.read(arguments);
|
||||
if (segment && this._segments.length)
|
||||
this.addSegment(segment);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a cubic bezier curve to the path, defined by two handles and a to
|
||||
* point.
|
||||
*/
|
||||
cubicCurveTo: function(handle1, handle2, to) {
|
||||
// First modify the current segment:
|
||||
var current = this.currentSegment;
|
||||
// Convert to relative values:
|
||||
current.handleOut = new Point(
|
||||
handle1.x - current.point.x,
|
||||
handle1.y - current.point.y);
|
||||
// And add the new segment, with handleIn set to c2
|
||||
this.addSegment(
|
||||
new Segment(to, handle2.subtract(to), new Point())
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Adds a quadratic bezier curve to the path, defined by a handle and a to
|
||||
* point.
|
||||
*/
|
||||
quadraticCurveTo: function(handle, to) {
|
||||
// This is exact:
|
||||
// If we have the three quad points: A E D,
|
||||
// and the cubic is A B C D,
|
||||
// B = E + 1/3 (A - E)
|
||||
// C = E + 1/3 (D - E)
|
||||
var current = this.currentSegment;
|
||||
var x1 = current.point.x;
|
||||
var y1 = current.point.y;
|
||||
this.cubicCurveTo(
|
||||
handle.add(current.point.subtract(handle).multiply(1/3)),
|
||||
handle.add(to.subtract(handle).multiply(1/3)),
|
||||
to
|
||||
);
|
||||
},
|
||||
|
||||
curveTo: function(through, to, parameter) {
|
||||
through = new Point(through);
|
||||
to = new Point(to);
|
||||
if (parameter == null)
|
||||
parameter = 0.5;
|
||||
var current = this.currentSegment.point;
|
||||
// handle = (through - (1 - t)^2 * current - t^2 * to) / (2 * (1 - t) * t)
|
||||
var t1 = 1 - parameter;
|
||||
var handle = through.subtract(
|
||||
current.multiply(t1 * t1)).subtract(
|
||||
to.multiply(parameter * parameter)).divide(
|
||||
2.0 * parameter * t1);
|
||||
if (handle.isNaN())
|
||||
throw new Error(
|
||||
"Cannot put a curve through points with parameter="
|
||||
+ parameter);
|
||||
this.quadraticCurveTo(handle, to);
|
||||
},
|
||||
|
||||
arcTo: function(to, clockwise) {
|
||||
var through, to;
|
||||
// Get the start point:
|
||||
var current = this.currentSegment;
|
||||
if (arguments[1] && typeof arguments[1] != 'boolean') {
|
||||
through = new Point(arguments[0]);
|
||||
to = new Point(arguments[1]);
|
||||
} else {
|
||||
if (clockwise === null)
|
||||
clockwise = true;
|
||||
var middle = current.point.add(to).divide(2);
|
||||
var step = middle.subtract(current.point);
|
||||
through = clockwise
|
||||
? middle.subtract(-step.y, step.x)
|
||||
: middle.add(-step.y, step.x);
|
||||
}
|
||||
|
||||
var x1 = current.point.x, x2 = through.x, x3 = to.x;
|
||||
var y1 = current.point.y, y2 = through.y, y3 = to.y;
|
||||
|
||||
var f = x3 * x3 - x3 * x2 - x1 * x3 + x1 * x2 + y3 * y3 - y3 * y2
|
||||
- y1 * y3 + y1 * y2;
|
||||
var g = x3 * y1 - x3 * y2 + x1 * y2 - x1 * y3 + x2 * y3 - x2 * y1;
|
||||
var m = g == 0 ? 0 : f / g;
|
||||
|
||||
var c = (m * y2) - x2 - x1 - (m * y1);
|
||||
var d = (m * x1) - y1 - y2 - (x2 * m);
|
||||
var e = (x1 * x2) + (y1 * y2) - (m * x1 * y2) + (m * x2 * y1);
|
||||
|
||||
var centerX = -c / 2;
|
||||
var centerY = -d / 2;
|
||||
var radius = Math.sqrt(centerX * centerX + centerY * centerY - e);
|
||||
|
||||
// Note: reversing the Y equations negates the angle to adjust
|
||||
// for the upside down coordinate system.
|
||||
var angle = Math.atan2(centerY - y1, x1 - centerX);
|
||||
var middle = Math.atan2(centerY - y2, x2 - centerX);
|
||||
var extent = Math.atan2(centerY - y3, x3 - centerX);
|
||||
|
||||
var diff = middle - angle;
|
||||
if (diff < -Math.PI)
|
||||
diff += Math.PI * 2;
|
||||
else if (diff > Math.PI)
|
||||
diff -= Math.PI * 2;
|
||||
|
||||
extent -= angle;
|
||||
if (extent <= 0.0)
|
||||
extent += Math.PI * 2;
|
||||
|
||||
if (diff < 0) extent = Math.PI * 2 - extent;
|
||||
else extent = -extent;
|
||||
angle = -angle;
|
||||
|
||||
var ext = Math.abs(extent);
|
||||
var arcSegs;
|
||||
if (ext >= 2 * Math.PI) arcSegs = 4;
|
||||
else arcSegs = Math.ceil(ext * 2 / Math.PI);
|
||||
|
||||
var inc = extent;
|
||||
if (inc > 2 * Math.PI) inc = 2 * Math.PI;
|
||||
else if (inc < -2 * Math.PI) inc = -2 * Math.PI;
|
||||
inc /= arcSegs;
|
||||
|
||||
var halfInc = inc / 2;
|
||||
var z = 4 / 3 * Math.sin(halfInc) / (1 + Math.cos(halfInc));
|
||||
|
||||
for (var i = 0; i <= arcSegs; i++) {
|
||||
var relx = Math.cos(angle);
|
||||
var rely = Math.sin(angle);
|
||||
var pt = new Point(centerX + relx * radius,
|
||||
centerY + rely * radius);
|
||||
var out;
|
||||
if (i == arcSegs) out = null;
|
||||
else out = new Point(centerX + (relx - z * rely) * radius - pt.x,
|
||||
centerY + (rely + z * relx) * radius - pt.y);
|
||||
if (i == 0) {
|
||||
// Modify startSegment
|
||||
current.handleOut = out;
|
||||
} else {
|
||||
// Add new Segment
|
||||
var inPoint = new Point(
|
||||
centerX + (relx + z * rely) * radius - pt.x,
|
||||
centerY + (rely - z * relx) * radius - pt.y);
|
||||
this.addSegment(new Segment(pt, inPoint, out));
|
||||
}
|
||||
angle += inc;
|
||||
}
|
||||
},
|
||||
|
||||
lineBy: function() {
|
||||
var vector = Point.read(arguments);
|
||||
if (vector) {
|
||||
var current = this.currentSegment;
|
||||
this.lineTo(current.point.add(vector));
|
||||
}
|
||||
},
|
||||
|
||||
smooth: function() {
|
||||
var segments = this._segments;
|
||||
|
||||
|
||||
// This code is based on the work by Oleg V. Polikarpotchkin,
|
||||
// http://ov-p.spaces.live.com/blog/cns!39D56F0C7A08D703!147.entry
|
||||
// It was extended to support closed paths by averaging overlapping
|
||||
|
@ -387,8 +436,8 @@ Path = PathItem.extend(new function() {
|
|||
for (var i = 0; i < size; i++)
|
||||
knots[i + overlap] = segments[i].point;
|
||||
if (this.closed) {
|
||||
// If we're averaging, add the 4 last points again at the beginning,
|
||||
// and the 4 first ones at the end.
|
||||
// If we're averaging, add the 4 last points again at the
|
||||
// beginning, and the 4 first ones at the end.
|
||||
for (var i = 0; i < overlap; i++) {
|
||||
knots[i] = segments[i + size - overlap].point;
|
||||
knots[i + size + overlap] = segments[i].point;
|
||||
|
@ -456,60 +505,13 @@ Path = PathItem.extend(new function() {
|
|||
segment.handleIn = handleIn.subtract(segment.point);
|
||||
}
|
||||
},
|
||||
|
||||
curveBy: function(throughVector, toVector, parameter) {
|
||||
throughVector = Point.read(throughVector);
|
||||
toVector = Point.read(toVector);
|
||||
var current = this.currentSegment.point;
|
||||
this.curveTo(current.add(throughVector), current.add(toVector), parameter);
|
||||
},
|
||||
|
||||
arcBy: function(throughVector, toVector) {
|
||||
throughVector = Point.read(throughVector);
|
||||
toVector = Point.read(toVector);
|
||||
var current = this.currentSegment.point;
|
||||
this.arcBy(current.add(throughVector), current.add(toVector));
|
||||
},
|
||||
|
||||
|
||||
setCtxStyles: function(ctx) {
|
||||
for (var i in styleNames) {
|
||||
var style;
|
||||
if (style = this[i])
|
||||
ctx[styleNames[i]] = style;
|
||||
}
|
||||
},
|
||||
|
||||
draw: function(ctx, compound) {
|
||||
if (!this.visible) return;
|
||||
if(!compound)
|
||||
ctx.beginPath();
|
||||
var cp1;
|
||||
for (var i = 0, l = this._segments.length; i < l; i++) {
|
||||
var segment = this._segments[i];
|
||||
var point = segment.point;
|
||||
var handleIn = segment.handleIn.add(point);
|
||||
var handleOut = segment.handleOut.add(point);
|
||||
if (i == 0) {
|
||||
ctx.moveTo(point.x, point.y);
|
||||
} else {
|
||||
ctx.bezierCurveTo(cp1.x, cp1.y, handleIn.x, handleIn.y,
|
||||
point.x, point.y);
|
||||
}
|
||||
cp1 = handleOut;
|
||||
}
|
||||
if (this.closed && this._segments.length > 1) {
|
||||
var segment = this._segments[0];
|
||||
var point = segment.point;
|
||||
var handleIn = segment.handleIn.add(point);
|
||||
ctx.bezierCurveTo(cp1.x, cp1.y, handleIn.x, handleIn.y,
|
||||
point.x, point.y);
|
||||
ctx.closePath();
|
||||
}
|
||||
if(!compound) {
|
||||
this.setCtxStyles(ctx);
|
||||
if (this.fillColor) ctx.fill();
|
||||
if (this.strokeColor) ctx.stroke();
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue