2011-03-06 19:50:44 -05:00
|
|
|
|
/*
|
|
|
|
|
* Paper.js
|
|
|
|
|
*
|
|
|
|
|
* This file is part of Paper.js, a JavaScript Vector Graphics Library,
|
|
|
|
|
* based on Scriptographer.org and designed to be largely API compatible.
|
|
|
|
|
* http://scriptographer.org/
|
|
|
|
|
*
|
|
|
|
|
* Copyright (c) 2011, Juerg Lehni & Jonathan Puckey
|
|
|
|
|
* http://lehni.org/ & http://jonathanpuckey.com/
|
|
|
|
|
*
|
|
|
|
|
* All rights reserved. See LICENSE file for details.
|
|
|
|
|
*/
|
|
|
|
|
|
2011-03-04 08:34:31 -05:00
|
|
|
|
var Curve = this.Curve = Base.extend({
|
2011-03-06 07:29:17 -05:00
|
|
|
|
beans: true,
|
|
|
|
|
|
2011-03-06 07:24:15 -05:00
|
|
|
|
initialize: function(arg0, arg1, arg2, arg3) {
|
|
|
|
|
if (arguments.length == 0) {
|
|
|
|
|
this._segment1 = new Segment();
|
|
|
|
|
this._segment2 = new Segment();
|
|
|
|
|
} else if (arguments.length == 1) {
|
|
|
|
|
// TODO: If beans are not activated, this won't copy from
|
|
|
|
|
// an existing segment. OK?
|
|
|
|
|
this._segment1 = new Segment(arg0.segment1);
|
|
|
|
|
this._segment2 = new Segment(arg0.segment2);
|
|
|
|
|
} else if (arguments.length == 2) {
|
|
|
|
|
if (arg0 instanceof Path) {
|
|
|
|
|
this._path = arg0;
|
|
|
|
|
this._index1 = arg1;
|
|
|
|
|
this._updateSegments();
|
|
|
|
|
} else {
|
|
|
|
|
this._segment1 = new Segment(arg0);
|
|
|
|
|
this._segment2 = new Segment(arg1);
|
|
|
|
|
}
|
|
|
|
|
} else if (arguments.length == 4) {
|
|
|
|
|
this._segment1 = new Segment(arg0, null, arg1);
|
|
|
|
|
this._segment2 = new Segment(arg3, arg2, null);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_updateSegments: function() {
|
|
|
|
|
if (this._path) {
|
|
|
|
|
this._index2 = this._index1 + 1;
|
|
|
|
|
// A closing curve?
|
|
|
|
|
var segments = this._path._segments;
|
|
|
|
|
if (this._index2 >= segments.length)
|
|
|
|
|
this._index2 = 0;
|
|
|
|
|
this._segment1 = segments[this._index1];
|
|
|
|
|
this._segment2 = segments[this._index2];
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The first anchor point of the curve.
|
|
|
|
|
*/
|
|
|
|
|
getPoint1: function() {
|
|
|
|
|
return this._segment1._point;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setPoint1: function() {
|
|
|
|
|
var point = Point.read(arguments);
|
|
|
|
|
this._segment1._point.set(point.x, point.y);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The second anchor point of the curve.
|
|
|
|
|
*/
|
|
|
|
|
getPoint2: function() {
|
|
|
|
|
return this._segment2._point;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setPoint2: function() {
|
|
|
|
|
var point = Point.read(arguments);
|
|
|
|
|
this._segment2._point.set(point.x, point.y);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The handle point that describes the tangent in the first anchor point.
|
|
|
|
|
*/
|
|
|
|
|
getHandle1: function() {
|
|
|
|
|
return this._segment1._handleOut;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setHandle1: function() {
|
|
|
|
|
var point = Point.read(arguments);
|
|
|
|
|
this._segment1._handleOut.set(point.x, point.y);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The handle point that describes the tangent in the second anchor point.
|
|
|
|
|
*/
|
|
|
|
|
getHandle2: function() {
|
|
|
|
|
return this._segment2._handleIn;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
setHandle2: function() {
|
|
|
|
|
var point = Point.read(arguments);
|
|
|
|
|
this._segment2._handleIn.set(point.x, point.y);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The first segment of the curve.
|
|
|
|
|
*/
|
|
|
|
|
getSegment1: function() {
|
|
|
|
|
return this._segment1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The second segment of the curve.
|
|
|
|
|
*/
|
|
|
|
|
getSegment2: function() {
|
|
|
|
|
return this._segment2;
|
2011-03-06 07:29:17 -05:00
|
|
|
|
},
|
|
|
|
|
|
2011-03-06 08:26:09 -05:00
|
|
|
|
getPath: function() {
|
|
|
|
|
return this._path;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getIndex: function() {
|
|
|
|
|
return this._index1;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
_setIndex: function(index) {
|
|
|
|
|
this._index1 = index;
|
|
|
|
|
this._updateSegments();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getNext: function() {
|
|
|
|
|
var curves = this._path && this._path._curves;
|
|
|
|
|
// TODO: Add cyclic looping when closed back to Scriptographer
|
|
|
|
|
return curves && (curves[this._index1 + 1]
|
|
|
|
|
|| this._path.closed && curves[0]) || null;
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getPrevious: function() {
|
|
|
|
|
var curves = this._path && this._path._curves;
|
|
|
|
|
return curves && (curves[this._index1 - 1]
|
|
|
|
|
|| this._path.closed && curves[curves.length - 1]) || null;
|
|
|
|
|
},
|
|
|
|
|
|
2011-03-06 19:00:45 -05:00
|
|
|
|
getCurveValues: function() {
|
|
|
|
|
var p1 = this._segment1._point,
|
|
|
|
|
h1 = this._segment1._handleOut,
|
|
|
|
|
h2 = this._segment2._handleIn,
|
|
|
|
|
p2 = this._segment2._point;
|
|
|
|
|
return [
|
|
|
|
|
p1.x, p1.y,
|
|
|
|
|
p1.x + h1.x, p1.y + h1.y,
|
|
|
|
|
p2.x + h2.x, p2.y + h2.y,
|
|
|
|
|
p2.x, p2.y
|
|
|
|
|
];
|
|
|
|
|
},
|
2011-03-06 07:29:17 -05:00
|
|
|
|
|
2011-03-06 21:22:33 -05:00
|
|
|
|
// TODO: Port back to Scriptographer, optionally suppporting from, to
|
|
|
|
|
// TODO: Replaces getPartLength(fromParameter, toParameter)?
|
|
|
|
|
getLength: function(from, to) {
|
2011-03-06 21:38:07 -05:00
|
|
|
|
var args = this.getCurveValues();
|
2011-03-06 21:22:33 -05:00
|
|
|
|
if (arguments.length > 0)
|
2011-03-06 21:38:07 -05:00
|
|
|
|
args.push(from, to);
|
|
|
|
|
return Curve.getLength.apply(Curve, args);
|
2011-03-06 07:56:47 -05:00
|
|
|
|
},
|
|
|
|
|
|
2011-03-06 18:24:33 -05:00
|
|
|
|
/**
|
|
|
|
|
* Checks if this curve is linear, meaning it does not define any curve
|
|
|
|
|
* handle.
|
|
|
|
|
|
|
|
|
|
* @return {@true if the curve is linear}
|
|
|
|
|
*/
|
|
|
|
|
isLinear: function() {
|
|
|
|
|
return this._segment1._handleOut.isZero()
|
|
|
|
|
&& this._segment2._handleIn.isZero();
|
|
|
|
|
},
|
|
|
|
|
|
2011-03-07 06:58:41 -05:00
|
|
|
|
// TODO: Port support for start parameter back to Scriptographer
|
|
|
|
|
getParameter: function(length, start) {
|
2011-03-06 21:38:07 -05:00
|
|
|
|
var args = this.getCurveValues();
|
2011-03-07 06:58:41 -05:00
|
|
|
|
args.push(length, start !== undefined ? start : length < 0 ? 1 : 0);
|
2011-03-06 21:38:07 -05:00
|
|
|
|
return Curve.getParameter.apply(Curve, args);
|
2011-03-06 19:00:45 -05:00
|
|
|
|
},
|
|
|
|
|
|
2011-03-06 18:24:33 -05:00
|
|
|
|
// TODO: getParameter(point, precision)
|
|
|
|
|
// TODO: getLocation
|
|
|
|
|
// TODO: getIntersections
|
|
|
|
|
// TODO: adjustThroughPoint
|
|
|
|
|
|
|
|
|
|
transform: function(matrix) {
|
|
|
|
|
return new Curve(
|
|
|
|
|
matrix.transform(this._segment1._point),
|
|
|
|
|
matrix.transform(this._segment1._handleOut),
|
|
|
|
|
matrix.transform(this._segment2._handleIn),
|
|
|
|
|
matrix.transform(this._segment2._point));
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
reverse: function() {
|
|
|
|
|
return new Curve(this._segment2.reverse(), this._segment1.reverse());
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
// TODO: divide
|
|
|
|
|
// TODO: split
|
|
|
|
|
|
2011-03-06 07:56:47 -05:00
|
|
|
|
clone: function() {
|
|
|
|
|
return new Curve(this._segment1, this._segment2);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
toString: function() {
|
|
|
|
|
return '{ point1: ' + this._segment1._point
|
|
|
|
|
+ (!this._segment1._handleOut.isZero()
|
|
|
|
|
? ', handle1: ' + this._segment1._handleOut : '')
|
|
|
|
|
+ (this._segment2._handleIn.isZero()
|
|
|
|
|
? ', handle2: ' + this._segment2._handleIn : '')
|
|
|
|
|
+ ', point2: ' + this._segment2._point
|
|
|
|
|
+ ' }';
|
2011-03-02 11:22:26 -05:00
|
|
|
|
}
|
2011-03-06 07:52:13 -05:00
|
|
|
|
}, new function() {
|
|
|
|
|
function evaluate(that, t, type) {
|
|
|
|
|
// Calculate the polynomial coefficients. caution: handles are relative
|
|
|
|
|
// to points
|
2011-03-06 08:07:49 -05:00
|
|
|
|
var point1 = that._segment1._point,
|
|
|
|
|
handle1 = that._segment1._handleOut,
|
|
|
|
|
handle2 = that._segment2._handleIn,
|
|
|
|
|
point2 = that._segment2._point,
|
2011-03-06 07:52:13 -05:00
|
|
|
|
x, y;
|
|
|
|
|
|
|
|
|
|
// Handle special case at beginning / end of curve
|
2011-03-06 09:45:44 -05:00
|
|
|
|
// TODO: Port back to Scriptographer, so 0.000000000001 won't be
|
|
|
|
|
// required anymore
|
2011-03-06 07:52:13 -05:00
|
|
|
|
if (t == 0 || t == 1) {
|
|
|
|
|
var point;
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 0: // point
|
|
|
|
|
point = t == 0 ? point1 : point2;
|
|
|
|
|
break;
|
|
|
|
|
case 1: // tangent
|
|
|
|
|
case 2: // normal
|
|
|
|
|
point = t == 0
|
|
|
|
|
? handle1.isZero()
|
|
|
|
|
? handle2.isZero()
|
|
|
|
|
? point2.subtract(point1)
|
|
|
|
|
: point2.add(handle2).subtract(point1)
|
|
|
|
|
: handle1
|
|
|
|
|
: handle2.isZero() // t == 1
|
|
|
|
|
? handle1.isZero()
|
|
|
|
|
? point1.subtract(point2)
|
|
|
|
|
: point1.add(handle1).subtract(point2)
|
|
|
|
|
: handle2;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
x = point.x;
|
|
|
|
|
y = point.y;
|
|
|
|
|
} else {
|
|
|
|
|
var dx = point2.x - point1.x,
|
|
|
|
|
cx = 3 * handle1.x,
|
|
|
|
|
bx = 3 * (dx + handle2.x - handle1.x) - cx,
|
|
|
|
|
ax = dx - cx - bx,
|
|
|
|
|
|
|
|
|
|
dy = point2.y - point1.y,
|
2011-03-06 20:37:18 -05:00
|
|
|
|
cy = 3 * handle1.y,
|
|
|
|
|
by = 3 * (dy + handle2.y - handle1.y) - cy,
|
2011-03-06 07:52:13 -05:00
|
|
|
|
ay = dy - cy - by;
|
|
|
|
|
|
|
|
|
|
switch (type) {
|
|
|
|
|
case 0: // point
|
|
|
|
|
x = ((ax * t + bx) * t + cx) * t + point1.x;
|
|
|
|
|
y = ((ay * t + by) * t + cy) * t + point1.y;
|
|
|
|
|
break;
|
|
|
|
|
case 1: // tangent
|
|
|
|
|
case 2: // normal
|
|
|
|
|
// Simply use the derivation of the bezier function for both
|
|
|
|
|
// the x and y coordinates:
|
|
|
|
|
x = (3 * ax * t + 2 * bx) * t + cx,
|
|
|
|
|
y = (3 * ay * t + 2 * by) * t + cy;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
// The normal is simply the rotated tangent:
|
2011-03-06 09:45:44 -05:00
|
|
|
|
// TODO: Rotate normals the other way in Scriptographer too?
|
|
|
|
|
// (Depending on orientation, I guess?)
|
|
|
|
|
return type == 2 ? new Point(y, -x) : new Point(x, y);
|
2011-03-06 07:52:13 -05:00
|
|
|
|
}
|
|
|
|
|
|
2011-03-06 21:22:33 -05:00
|
|
|
|
function getLengthIntegrand(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
|
|
|
|
|
// Calculate the coefficients of a Bezier derivative.
|
|
|
|
|
var ax = 9 * (c1x - c2x) + 3 * (p2x - p1x),
|
|
|
|
|
bx = 6 * (p1x + c2x) - 12 * c1x,
|
|
|
|
|
cx = 3 * (c1x - p1x),
|
|
|
|
|
|
|
|
|
|
ay = 9 * (c1y - c2y) + 3 * (p2y - p1y),
|
|
|
|
|
by = 6 * (p1y + c2y) - 12 * c1y,
|
|
|
|
|
cy = 3 * (c1y - p1y);
|
|
|
|
|
|
|
|
|
|
return function(t) {
|
|
|
|
|
// Calculate quadratic equations of derivatives for x and y
|
|
|
|
|
var dx = (ax * t + bx) * t + cx,
|
|
|
|
|
dy = (ay * t + by) * t + cy;
|
|
|
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2011-03-06 07:52:13 -05:00
|
|
|
|
return {
|
|
|
|
|
getPoint: function(parameter) {
|
|
|
|
|
return evaluate(this, parameter, 0);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getTangent: function(parameter) {
|
|
|
|
|
return evaluate(this, parameter, 1);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getNormal: function(parameter) {
|
|
|
|
|
return evaluate(this, parameter, 2);
|
2011-03-06 21:22:33 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
statics: {
|
|
|
|
|
getLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, a, b) {
|
2011-03-07 06:52:04 -05:00
|
|
|
|
if (a === undefined)
|
2011-03-06 21:22:33 -05:00
|
|
|
|
a = 0;
|
2011-03-07 06:52:04 -05:00
|
|
|
|
if (b === undefined)
|
2011-03-06 21:22:33 -05:00
|
|
|
|
b = 1;
|
|
|
|
|
if (p1x == c1x && p1y == c1y && p2x == c2x && p2y == c2y) {
|
|
|
|
|
// Straight line
|
2011-03-06 21:33:36 -05:00
|
|
|
|
var dx = p2x - p1x,
|
|
|
|
|
dy = p2y - p1y;
|
2011-03-07 06:37:42 -05:00
|
|
|
|
return (b - a) * Math.sqrt(dx * dx + dy * dy);
|
2011-03-06 21:22:33 -05:00
|
|
|
|
}
|
|
|
|
|
var ds = getLengthIntegrand(
|
|
|
|
|
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y);
|
2011-03-07 06:12:00 -05:00
|
|
|
|
return Numerical.integrate(ds, a, b, 8);
|
2011-03-06 21:22:33 -05:00
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
getParameter: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y,
|
2011-03-07 06:58:41 -05:00
|
|
|
|
length, start) {
|
2011-03-07 06:37:42 -05:00
|
|
|
|
if (length == 0) {
|
2011-03-07 06:58:41 -05:00
|
|
|
|
return start;
|
2011-03-07 06:37:42 -05:00
|
|
|
|
}
|
2011-03-06 21:26:59 -05:00
|
|
|
|
if (p1x == c1x && p1y == c1y && p2x == c2x && p2y == c2y) {
|
|
|
|
|
// Straight line, calculate directly
|
2011-03-06 21:28:49 -05:00
|
|
|
|
// t = length / lineLength:
|
2011-03-06 21:26:59 -05:00
|
|
|
|
var dx = p2x - p1x,
|
|
|
|
|
dy = p2y - p1y;
|
2011-03-07 06:58:41 -05:00
|
|
|
|
return Math.max(Math.min(start
|
|
|
|
|
+ length / Math.sqrt(dx * dx + dy * dy), 0, 1));
|
2011-03-06 21:26:59 -05:00
|
|
|
|
}
|
2011-03-06 21:38:07 -05:00
|
|
|
|
// Let's use the Van Wijngaarden–Dekker–Brent Method to find
|
|
|
|
|
// solutions more reliably than with False Position Method.
|
2011-03-07 06:37:42 -05:00
|
|
|
|
// The precision of 5 iterations seems enough for this
|
2011-03-07 06:51:01 -05:00
|
|
|
|
var forward = length > 0,
|
2011-03-07 06:37:42 -05:00
|
|
|
|
// Use integrand to calculate both range length and part
|
|
|
|
|
// lengths in f(t) below.
|
|
|
|
|
ds = getLengthIntegrand(
|
2011-03-07 06:51:01 -05:00
|
|
|
|
p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y),
|
|
|
|
|
a, b, f;
|
|
|
|
|
// See if we're going forward or backward, and handle cases
|
|
|
|
|
// differently
|
2011-03-07 06:37:42 -05:00
|
|
|
|
if (forward) { // Normal way
|
2011-03-07 06:58:41 -05:00
|
|
|
|
a = start;
|
2011-03-07 06:37:42 -05:00
|
|
|
|
b = 1;
|
2011-03-07 06:51:01 -05:00
|
|
|
|
// We're moving b to the right to find root for length
|
2011-03-07 06:37:42 -05:00
|
|
|
|
f = function(t) {
|
|
|
|
|
return length - Numerical.integrate(ds, a, t, 5);
|
|
|
|
|
}
|
|
|
|
|
} else { // Going backwards
|
|
|
|
|
a = 0;
|
2011-03-07 06:58:41 -05:00
|
|
|
|
b = start;
|
2011-03-07 06:37:42 -05:00
|
|
|
|
length = -length;
|
2011-03-07 06:51:01 -05:00
|
|
|
|
// We're moving a to the left to find root for length
|
2011-03-07 06:37:42 -05:00
|
|
|
|
f = function(t) {
|
|
|
|
|
return length - Numerical.integrate(ds, t, b, 5);
|
|
|
|
|
}
|
2011-03-06 21:22:33 -05:00
|
|
|
|
}
|
2011-03-07 06:37:42 -05:00
|
|
|
|
var rangeLength = Numerical.integrate(ds, a, b, 8);
|
|
|
|
|
if (length >= rangeLength)
|
|
|
|
|
return forward ? b : a;
|
|
|
|
|
// Use length / rangeLength for an initial guess for t, to
|
2011-03-06 21:22:33 -05:00
|
|
|
|
// bring us closer:
|
2011-03-07 06:37:42 -05:00
|
|
|
|
var guess = length / rangeLength;
|
|
|
|
|
return Numerical.findRoot(f,
|
|
|
|
|
forward ? a : b - guess, // a
|
|
|
|
|
forward ? a + guess : b, // b
|
2011-03-06 21:22:33 -05:00
|
|
|
|
Numerical.TOLERANCE);
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
subdivide: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
|
|
|
|
|
var u = 1 - t,
|
|
|
|
|
// Interpolate from 4 to 3 points
|
|
|
|
|
p3x = u * p1x + t * c1x,
|
|
|
|
|
p3y = u * p1y + t * c1y,
|
|
|
|
|
p4x = u * c1x + t * c2x,
|
|
|
|
|
p4y = u * c1y + t * c2y,
|
|
|
|
|
p5x = u * c2x + t * p2x,
|
|
|
|
|
p5y = u * c2y + t * p2y,
|
|
|
|
|
// Interpolate from 3 to 2 points
|
|
|
|
|
p6x = u * p3x + t * p4x,
|
|
|
|
|
p6y = u * p3y + t * p4y,
|
|
|
|
|
p7x = u * p4x + t * p5x,
|
|
|
|
|
p7y = u * p4y + t * p5y,
|
|
|
|
|
// Interpolate from 2 points to 1 point
|
|
|
|
|
p8x = u * p6x + t * p7x,
|
|
|
|
|
p8y = u * p6y + t * p7y;
|
2011-03-06 21:38:07 -05:00
|
|
|
|
// We now have all the values we need to build the subcurves:
|
2011-03-06 21:22:33 -05:00
|
|
|
|
return [
|
|
|
|
|
[p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], // left
|
|
|
|
|
[p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] // right
|
|
|
|
|
];
|
|
|
|
|
}
|
2011-03-06 07:52:13 -05:00
|
|
|
|
}
|
|
|
|
|
};
|
2011-03-02 11:22:26 -05:00
|
|
|
|
});
|