Expose the previously private evalutate() function through Curve.evaluate(), make it work with curve value arrays, and use it the for various evaluation methods (#getPoint/Tangent/Normal).

This commit is contained in:
Jürg Lehni 2011-06-05 12:37:43 +01:00
parent 14816a872e
commit cb3834f41c

View file

@ -260,6 +260,43 @@ var Curve = this.Curve = Base.extend({
return Curve.getParameter.apply(Curve, args); return Curve.getParameter.apply(Curve, args);
}, },
_evaluate: function(parameter, type) {
var args = this.getCurveValues();
args.push(parameter, type);
return Curve.evaluate.apply(Curve, args);
},
/**
* Returns the point on the curve at the specified position.
*
* @param {Number} parameter the position at which to find the point as
* a value between {@code 0} and {@code 1}.
* @return {Point}
*/
getPoint: function(parameter) {
return this._evaluate(parameter, 0);
},
/**
* Returns the tangent point on the curve at the specified position.
*
* @param {Number} parameter the position at which to find the tangent
* point as a value between {@code 0} and {@code 1}.
*/
getTangent: function(parameter) {
return this._evaluate(parameter, 1);
},
/**
* Returns the normal point on the curve at the specified position.
*
* @param {Number} parameter the position at which to find the normal
* point as a value between {@code 0} and {@code 1}.
*/
getNormal: function(parameter) {
return this._evaluate(parameter, 2);
},
// TODO: getParameter(point, precision) // TODO: getParameter(point, precision)
// TODO: getLocation // TODO: getLocation
// TODO: getIntersections // TODO: getIntersections
@ -320,18 +357,23 @@ var Curve = this.Curve = Base.extend({
curve._segment1 = segment1; curve._segment1 = segment1;
curve._segment2 = segment2; curve._segment2 = segment2;
return curve; return curve;
} },
}
}, new function() {
function evaluate(that, t, type) { getCurveValues: function(segment1, segment2) {
// Calculate the polynomial coefficients. caution: handles are relative var p1 = segment1._point,
// to points h1 = segment1._handleOut,
var point1 = that._segment1._point, h2 = segment2._handleIn,
handle1 = that._segment1._handleOut, p2 = segment2._point;
handle2 = that._segment2._handleIn, return [
point2 = that._segment2._point, p1._x, p1._y,
x, y; p1._x + h1._x, p1._y + h1._y,
p2._x + h2._x, p2._y + h2._y,
p2._x, p2._y
];
},
evaluate: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t, type) {
var x, y;
// Handle special case at beginning / end of curve // Handle special case at beginning / end of curve
// PORT: Change in Sg too, so 0.000000000001 won't be // PORT: Change in Sg too, so 0.000000000001 won't be
@ -340,40 +382,54 @@ var Curve = this.Curve = Base.extend({
var point; var point;
switch (type) { switch (type) {
case 0: // point case 0: // point
point = t == 0 ? point1 : point2; x = t == 0 ? p1x : p2x;
y = t == 0 ? p1y : p2y;
break; break;
case 1: // tangent case 1: // tangent
case 2: // normal case 2: // normal
point = t == 0 var px, py;
? handle1.isZero() if (t == 0) {
? handle2.isZero() if (c1x == p1x && c1y == p1y) { // handle1 = 0
? point2.subtract(point1) if (c2x == p2x && c2y == p2y) { // handle2 = 0
: point2.add(handle2).subtract(point1) px = p2x; py = p2y; // p2
: handle1 } else {
: handle2.isZero() // t == 1 px = c2x; py = c2y; // c2
? handle1.isZero() }
? point1.subtract(point2) } else {
: point1.add(handle1).subtract(point2) px = c1x; py = c1y; // handle1
: handle2; }
x = px - p1x;
y = py - p1y;
} else {
if (c2x == p2x && c2y == p2y) { // handle2 = 0
if (c1x == p1x && c1y == p1y) { // handle1 = 0
px = p1x; py = p1y; // p1
} else {
px = c1x; py = c1y; // c1
}
} else { // handle2
px = c2x; py = c2y;
}
x = px - p2x;
y = py - p2y;
}
break; break;
} }
x = point._x;
y = point._y;
} else { } else {
var dx = point2._x - point1._x, // Calculate the polynomial coefficients.
cx = 3 * handle1._x, var cx = 3 * (c1x - p1x),
bx = 3 * (dx + handle2._x - handle1._x) - cx, bx = 3 * (c2x - c1x) - cx,
ax = dx - cx - bx, ax = p2x - p1x - cx - bx,
dy = point2._y - point1._y, cy = 3 * (c1y - p1y),
cy = 3 * handle1._y, by = 3 * (c2y - c1y) - cy,
by = 3 * (dy + handle2._y - handle1._y) - cy, ay = p2y - p1y - cy - by;
ay = dy - cy - by;
switch (type) { switch (type) {
case 0: // point case 0: // point
x = ((ax * t + bx) * t + cx) * t + point1._x; // Calculate the curve point at parameter value t
y = ((ay * t + by) * t + cy) * t + point1._y; x = ((ax * t + bx) * t + cx) * t + p1x;
y = ((ay * t + by) * t + cy) * t + p1y;
break; break;
case 1: // tangent case 1: // tangent
case 2: // normal case 2: // normal
@ -388,7 +444,91 @@ var Curve = this.Curve = Base.extend({
// TODO: Rotate normals the other way in Scriptographer too? // TODO: Rotate normals the other way in Scriptographer too?
// (Depending on orientation, I guess?) // (Depending on orientation, I guess?)
return type == 2 ? new Point(y, -x) : new Point(x, y); return type == 2 ? new Point(y, -x) : new Point(x, y);
},
subdivide: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
if (t === undefined)
t = 0.5;
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;
// We now have all the values we need to build the subcurves:
return [
[p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], // left
[p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] // right
];
},
// TODO: Find better name
getPart: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, from, to) {
var curve = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
if (from > 0) {
// 8th argument of Curve.subdivide() == t, and values can be
// directly used as arguments list for apply().
curve[8] = from;
curve = Curve.subdivide.apply(Curve, curve)[1]; // right
} }
if (to < 1) {
// Se above about curve[8].
// Interpolate the parameter at 'to' in the new curve and
// cut there
curve[8] = (to - from) / (1 - from);
curve = Curve.subdivide.apply(Curve, curve)[0]; // left
}
return curve;
},
isSufficientlyFlat: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
// Inspired by Skia, but to be tested:
// Calculate 1/3 (m1) and 2/3 (m2) along the line between start (p1)
// and end (p2), measure distance from there the control points and
// see if they are further away than 1/2.
// Seems all very inaccurate, especially since the distance
// measurement is just the bigger one of x / y...
// TODO: Find a more accurate and still fast way to determine this.
var vx = (p2x - p1x) / 3,
vy = (p2y - p1y) / 3,
m1x = p1x + vx,
m1y = p1y + vy,
m2x = p2x - vx,
m2y = p2y - vy;
return Math.max(
Math.abs(m1x - c1x), Math.abs(m1y - c1y),
Math.abs(m2x - c1x), Math.abs(m1y - c1y)) < 1 / 2;
/*
// Thanks to Kaspar Fischer for the following:
// http://www.inf.ethz.ch/personal/fischerk/pubs/bez.pdf
var ux = 3 * c1x - 2 * p1x - p2x;
ux *= ux;
var uy = 3 * c1y - 2 * p1y - p2y;
uy *= uy;
var vx = 3 * c2x - 2 * p2x - p1x;
vx *= vx;
var vy = 3 * c2y - 2 * p2y - p1y;
vy *= vy;
if (ux < vx)
ux = vx;
if (uy < vy)
uy = vy;
// Tolerance is 16 * tol ^ 2
return ux + uy <= 16 * Numerical.TOLERNACE * Numerical.TOLERNACE;
*/
}
}
}, new function() {
function getLengthIntegrand(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) { function getLengthIntegrand(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
// Calculate the coefficients of a Bezier derivative. // Calculate the coefficients of a Bezier derivative.
@ -417,52 +557,7 @@ var Curve = this.Curve = Base.extend({
} }
return { return {
/** @lends Curve# */ statics: true,
/**
* Returns the point on the curve at the specified position.
*
* @param {Number} parameter the position at which to find the point as
* a value between {@code 0} and {@code 1}.
* @return {Point}
*/
getPoint: function(parameter) {
return evaluate(this, parameter, 0);
},
/**
* Returns the tangent point on the curve at the specified position.
*
* @param {Number} parameter the position at which to find the tangent
* point as a value between {@code 0} and {@code 1}.
*/
getTangent: function(parameter) {
return evaluate(this, parameter, 1);
},
/**
* Returns the normal point on the curve at the specified position.
*
* @param {Number} parameter the position at which to find the normal
* point as a value between {@code 0} and {@code 1}.
*/
getNormal: function(parameter) {
return evaluate(this, parameter, 2);
},
statics: {
getCurveValues: function(segment1, segment2) {
var p1 = segment1._point,
h1 = segment1._handleOut,
h2 = segment2._handleIn,
p2 = 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
];
},
getLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, a, b) { getLength: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, a, b) {
if (a === undefined) if (a === undefined)
@ -520,90 +615,6 @@ var Curve = this.Curve = Base.extend({
return Numerical.findRoot(f, ds, return Numerical.findRoot(f, ds,
forward ? a + guess : b - guess, // Initial guess for x forward ? a + guess : b - guess, // Initial guess for x
a, b, 16, Numerical.TOLERANCE); a, b, 16, Numerical.TOLERANCE);
},
subdivide: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, t) {
if (t === undefined)
t = 0.5;
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;
// We now have all the values we need to build the subcurves:
return [
[p1x, p1y, p3x, p3y, p6x, p6y, p8x, p8y], // left
[p8x, p8y, p7x, p7y, p5x, p5y, p2x, p2y] // right
];
},
// TODO: Find better name
getPart: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y, from, to) {
var curve = [p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y];
if (from > 0) {
// 8th argument of Curve.subdivide() == t, and values can be
// directly used as arguments list for apply().
curve[8] = from;
curve = Curve.subdivide.apply(Curve, curve)[1]; // right
}
if (to < 1) {
// Se above about curve[8].
// Interpolate the parameter at 'to' in the new curve and
// cut there
curve[8] = (to - from) / (1 - from);
curve = Curve.subdivide.apply(Curve, curve)[0]; // left
}
return curve;
},
isSufficientlyFlat: function(p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y) {
// Inspired by Skia, but to be tested:
// Calculate 1/3 (m1) and 2/3 (m2) along the line between start
// (p1) and end (p2), measure distance from there the control
// points and see if they are further away than 1/2.
// Seems all very inaccurate, especially since the distance
// measurement is just the bigger one of x / y...
// TODO: Find a more accurate and still fast way to determine
// this.
var vx = (p2x - p1x) / 3,
vy = (p2y - p1y) / 3,
m1x = p1x + vx,
m1y = p1y + vy,
m2x = p2x - vx,
m2y = p2y - vy;
return Math.max(
Math.abs(m1x - c1x), Math.abs(m1y - c1y),
Math.abs(m2x - c1x), Math.abs(m1y - c1y)) < 1 / 2;
/*
// Thanks to Kaspar Fischer for the following:
// http://www.inf.ethz.ch/personal/fischerk/pubs/bez.pdf
var ux = 3 * c1x - 2 * p1x - p2x;
ux *= ux;
var uy = 3 * c1y - 2 * p1y - p2y;
uy *= uy;
var vx = 3 * c2x - 2 * p2x - p1x;
vx *= vx;
var vy = 3 * c2y - 2 * p2y - p1y;
vy *= vy;
if (ux < vx)
ux = vx;
if (uy < vy)
uy = vy;
// Tolerance is 16 * tol ^ 2
return ux + uy <= 16 * Numerical.TOLERNACE * Numerical.TOLERNACE;
*/
}
} }
}; };
}); });