From 80f9f6061ce1fe9cb83f26d4eb01646a677ddf22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Sun, 26 May 2013 15:44:52 -0700 Subject: [PATCH] Change the way the Line class handles direction vectors and infinite lines. The beginning of performance improvements in the Line class. --- src/basic/Line.js | 116 +++++++++++++++++++----------------------- src/path/Curve.js | 8 +-- src/path/Path.js | 14 ++--- src/svg/SVGExport.js | 6 +-- src/util/Formatter.js | 2 + 5 files changed, 66 insertions(+), 80 deletions(-) diff --git a/src/basic/Line.js b/src/basic/Line.js index 9070bd6a..ea51cb4b 100644 --- a/src/basic/Line.js +++ b/src/basic/Line.js @@ -22,26 +22,13 @@ var Line = this.Line = Base.extend(/** @lends Line# */{ * * @param {Point} point1 * @param {Point} point2 - * @param {Boolean} [infinite=true] + * @param {Boolean} [asVector=false] */ - initialize: function(point1, point2, infinite) { - // Convention: With 3 parameters, both points are absolute, and infinite - // controls wether the line extends beyond the defining points, meaning - // intersection outside the line segment are allowed. - // With two parameters, the 2nd parameter is a direction, and infinite - // is automatially true, since we're describing an infinite line. - var _point1 = Point.read(arguments), - _point2 = Point.read(arguments), - _infinite = Base.read(arguments); - if (_infinite !== undefined) { - this.point = _point1; - this.vector = _point2.subtract(_point1); - this.infinite = _infinite; - } else { - this.point = _point1; - this.vector = _point2; - this.infinite = true; - } + initialize: function(point1, point2, asVector) { + this.point = Point.read(arguments); + this.vector = Point.read(arguments); + if (!Base.read(arguments)) + this.vector = this.vector.subtract(this.point); }, /** @@ -58,32 +45,19 @@ var Line = this.Line = Base.extend(/** @lends Line# */{ * @type Point */ - /** - * Specifies whether the line extends infinitely - * - * @name Line#infinite - * @type Boolean - */ - /** * @param {Line} line + * @param {Boolean} [isInfinite=false] * @return {Point} the intersection point of the lines, {@code undefined} * if the two lines are colinear, or {@code null} if they don't intersect. */ - intersect: function(line) { - var cross = this.vector.cross(line.vector); - // Avoid divisions by 0, and errors when getting too close to 0 - if (Numerical.isZero(cross)) - return undefined; - var v = line.point.subtract(this.point), - t1 = v.cross(line.vector) / cross, - t2 = v.cross(this.vector) / cross; - // Check the ranges of t parameters if the line is not allowed to - // extend beyond the definition points. - return (this.infinite || 0 <= t1 && t1 <= 1) - && (line.infinite || 0 <= t2 && t2 <= 1) - ? this.point.add(this.vector.multiply(t1)) - : null; + intersect: function(line, isInfinite) { + var p1 = this.point, + v1 = this.vector, + p2 = line.point, + v2 = line.vector; + return Line.intersect(p1.x, p1.y, v1.x, v1.y, p2.x, p2.y, v2.x, v2.y, + true, isInfinite); }, // DOCS: document Line#getSide(point) @@ -92,6 +66,7 @@ var Line = this.Line = Base.extend(/** @lends Line# */{ * @return {Number} */ getSide: function(point) { + point = Point.read(arguments); var v1 = this.vector, v2 = point.subtract(this.point), ccw = v2.cross(v1); @@ -100,44 +75,57 @@ var Line = this.Line = Base.extend(/** @lends Line# */{ if (ccw > 0) { ccw = v2.subtract(v1).dot(v1); if (ccw < 0) - ccw = 0; + ccw = 0; } } return ccw < 0 ? -1 : ccw > 0 ? 1 : 0; }, + // DOCS: document Line#getSignedDistance(point) + /** + * @param {Point} point + * @return {Number} + */ + getSignedDistance: function(point) { + var m = this.vector.y / this.vector.x, // slope + b = this.point.y - (m * this.point.x); // y offset + // Distance to the linear equation + return (point.y - (m * point.x) - b) / Math.sqrt(m * m + 1); + }, + // DOCS: document Line#getDistance(point) /** * @param {Point} point * @return {Number} */ getDistance: function(point) { - var m = this.vector.y / this.vector.x, // slope - b = this.point.y - (m * this.point.x); // y offset - // Distance to the linear equation - var dist = Math.abs(point.y - (m * point.x) - b) / Math.sqrt(m * m + 1); - return this.infinite ? dist : Math.min(dist, - point.getDistance(this.point), - point.getDistance(this.point.add(this.vector))); + return Math.abs(this.getSignedDistance(point)); }, - statics: { - intersect: function(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, infinite) { - var adx = ax2 - ax1, - ady = ay2 - ay1, - bdx = bx2 - bx1, - bdy = by2 - by1, - dx = ax1 - bx1, - dy = ay1 - by1, - cross = bdy * adx - bdx * ady; + statics: /** @lends Line */{ + intersect: function(ax1, ay1, ax2, ay2, bx1, by1, bx2, by2, asVectors, + isInfinite) { + // Convert 2nd points to vectors if they are not specified as such. + if (!asVectors) { + ax2 -= ax1; + ay2 -= ay1; + bx2 -= bx1; + by2 -= by1; + } + var cross = by2 * ax2 - bx2 * ay2; + // Avoid divisions by 0, and errors when getting too close to 0 if (!Numerical.isZero(cross)) { - var ta = (bdx * dy - bdy * dx) / cross, - tb = (adx * dy - ady * dx) / cross; - if ((infinite || 0 <= ta && ta <= 1) - && (infinite || 0 <= tb && tb <= 1)) - return Point.create( - ax1 + ta * adx, - ay1 + ta * ady); + var dx = ax1 - bx1, + dy = ay1 - by1, + ta = (bx2 * dy - by2 * dx) / cross, + tb = (ax2 * dy - ay2 * dx) / cross; + // Check the ranges of t parameters if the line is not allowed + // to extend beyond the definition points. + if ((isInfinite || 0 <= ta && ta <= 1) + && (isInfinite || 0 <= tb && tb <= 1)) + return Point.create( + ax1 + ta * ax2, + ay1 + ta * ay2); } } } diff --git a/src/path/Curve.js b/src/path/Curve.js index 4883069f..25bf5e71 100644 --- a/src/path/Curve.js +++ b/src/path/Curve.js @@ -1162,13 +1162,9 @@ new function() { // Scope for methods that require numerical integration && (Curve.isLinear(v2) || Curve.isFlatEnough(v2, /*#=*/ Numerical.TOLERANCE))) { // See if the parametric equations of the lines interesct. - // var point = new Line(v1[0], v1[1], v1[6], v1[7], false) - // .intersect(new Line(v2[0], v2[1], v2[6], v2[7], false)); - // Use static version without creation of Line objects, but it - // doesn't seem to yield measurable speed improvements! var point = Line.intersect( v1[0], v1[1], v1[6], v1[7], - v2[0], v2[1], v2[6], v2[7], false); + v2[0], v2[1], v2[6], v2[7]); if (point) addLocation(locations, curve1, null, point, curve2); } else { @@ -1439,7 +1435,7 @@ new function() { // Scope for methods that require numerical integration function getLineLineIntersection(v1, v2, curve1, curve2, locations) { var point = Line.intersect( v1[0], v1[1], v1[6], v1[7], - v2[0], v2[1], v2[6], v2[7], false); + v2[0], v2[1], v2[6], v2[7]); // Passing null for parameter leads to lazy determination of parameter // values in CurveLocation#getParameter() only once they are requested. if (point) diff --git a/src/path/Path.js b/src/path/Path.js index de5afa2c..e844e3a6 100644 --- a/src/path/Path.js +++ b/src/path/Path.js @@ -2104,11 +2104,11 @@ var Path = this.Path = PathItem.extend(/** @lends Path# */{ // Construct the two perpendicular middle lines to (from, through) // and (through, to), and intersect them to get the center var l1 = new Line(from.add(through).divide(2), - through.subtract(from).rotate(90)), + through.subtract(from).rotate(90), true), l2 = new Line(through.add(to).divide(2), - to.subtract(through).rotate(90)), - center = l1.intersect(l2), - line = new Line(from, to, true), + to.subtract(through).rotate(90), true), + center = l1.intersect(l2, true), + line = new Line(from, to), throughSide = line.getSide(through); if (!center) { // If the two lines are colinear, there cannot be an arc as the @@ -2389,10 +2389,10 @@ statics: { normal2 = curve2.getNormalAt(0, true).normalize(miterRadius), // Intersect the two lines line1 = new Line(point.add(normal1), - Point.create(-normal1.y, normal1.x)), + Point.create(-normal1.y, normal1.x), true), line2 = new Line(point.add(normal2), - Point.create(-normal2.y, normal2.x)), - corner = line1.intersect(line2); + Point.create(-normal2.y, normal2.x), true), + corner = line1.intersect(line2, true); // Now measure the distance from the segment to the // intersection, which his half of the miter distance if (!corner || point.getDistance(corner) > miterLimit) { diff --git a/src/svg/SVGExport.js b/src/svg/SVGExport.js index c2743b9c..053173b0 100644 --- a/src/svg/SVGExport.js +++ b/src/svg/SVGExport.js @@ -122,10 +122,10 @@ new function() { if (handle1.isOrthogonal(handle2)) { var from = segment._point, to = next._point, - // Find hte corner point by intersecting the lines described + // Find the corner point by intersecting the lines described // by both handles: - corner = new Line(from, handle1).intersect( - new Line(to, handle2)); + corner = new Line(from, handle1, true).intersect( + new Line(to, handle2, true), true); return corner && Numerical.isZero(handle1.getLength() / corner.subtract(from).getLength() - kappa) && Numerical.isZero(handle2.getLength() / diff --git a/src/util/Formatter.js b/src/util/Formatter.js index d9261d1a..d26069b5 100644 --- a/src/util/Formatter.js +++ b/src/util/Formatter.js @@ -30,6 +30,8 @@ var Formatter = Base.extend({ * @param {Number} num the number to be converted to a string */ number: function(val) { + // It would be nice to use Number#toFixed() instead, but it pads with 0, + // unecessarily consuming space. return Math.round(val * this.multiplier) / this.multiplier; },