+
+ Path Tangents To Vector
+
+
+
+
+
+
+
+
diff --git a/src/path/Curve.js b/src/path/Curve.js
index 188b34c9..b2f58b0b 100644
--- a/src/path/Curve.js
+++ b/src/path/Curve.js
@@ -1147,6 +1147,23 @@ statics: /** @lends Curve */{
*/
getParameterAt: '#getTimeAt',
+ /**
+ * Calculates the curve-time parameters where the curve is tangential to
+ * provided tangent. Note that tangents at the start or end are included.
+ *
+ * @param {Point} tangent the tangent to which the curve must be tangential
+ * @return {Number[]} at most two curve-time parameters, where the curve is
+ * tangential to the given tangent
+ */
+ getTimesWithTangent: function (/* tangent */) {
+ var vector = Point.read(arguments);
+ if (vector.isZero()) {
+ return [];
+ }
+
+ return Curve.getTimesWithTangent(this.getValues(), vector);
+ },
+
/**
* Calculates the curve offset at the specified curve-time parameter on
* the curve.
@@ -2230,6 +2247,56 @@ new function() { // Scope for bezier intersection using fat-line clipping
return pairs;
}
+ /**
+ * Internal method to calculates the curve-time parameters where the curve
+ * is tangential to provided tangent.
+ * Tangents at the start or end are included.
+ *
+ * @param {Number[]} v curve values
+ * @param {Point} point the tangent to which the curve must be tangential
+ * @return {Number[]} at most two curve-time parameters, where the curve is
+ * tangential to the given tangent
+ */
+ function getTimesWithTangent(v, point) {
+ // Algorithm adapted from: https://stackoverflow.com/a/34837312/7615922
+ var x0 = v[0], y0 = v[1],
+ x1 = v[2], y1 = v[3],
+ x2 = v[4], y2 = v[5],
+ x3 = v[6], y3 = v[7],
+ normalized = point.normalize(),
+ tx = normalized.x,
+ ty = normalized.y,
+ ax = 3 * x3 - 9 * x2 + 9 * x1 - 3 * x0,
+ ay = 3 * y3 - 9 * y2 + 9 * y1 - 3 * y0,
+ bx = 6 * x2 - 12 * x1 + 6 * x0,
+ by = 6 * y2 - 12 * y1 + 6 * y0,
+ cx = 3 * x1 - 3 * x0,
+ cy = 3 * y1 - 3 * y0,
+ den = 2 * ax * ty - 2 * ay * tx,
+ times = [];
+ if (Math.abs(den) < Numerical.CURVETIME_EPSILON) {
+ var num = ax * cy - ay * cx;
+ var den = ax * by - ay * bx;
+ if (den != 0) {
+ var t = -num / den;
+ if (t >= 0 && t <= 1) times.push(t);
+ }
+ } else {
+ var delta = (bx * bx - 4 * ax * cx) * ty * ty +
+ (-2 * bx * by + 4 * ay * cx + 4 * ax * cy) * tx * ty +
+ (by * by - 4 * ay * cy) * tx * tx;
+ var k = bx * ty - by * tx;
+ if (delta >= 0 && den != 0) {
+ var d = Math.sqrt(delta);
+ var t0 = -(k + d) / den;
+ var t1 = (-k + d) / den;
+ if (t0 >= 0 && t0 <= 1) times.push(t0);
+ if (t1 >= 0 && t1 <= 1) times.push(t1);
+ }
+ }
+ return times;
+ }
+
return /** @lends Curve# */{
/**
* Returns all intersections between two {@link Curve} objects as an
@@ -2252,7 +2319,8 @@ new function() { // Scope for bezier intersection using fat-line clipping
getOverlaps: getOverlaps,
// Exposed for use in boolean offsetting
getIntersections: getIntersections,
- getCurveLineIntersections: getCurveLineIntersections
+ getCurveLineIntersections: getCurveLineIntersections,
+ getTimesWithTangent: getTimesWithTangent
}
};
});
diff --git a/src/path/Path.js b/src/path/Path.js
index a4d02ca2..200811e2 100644
--- a/src/path/Path.js
+++ b/src/path/Path.js
@@ -1895,7 +1895,7 @@ var Path = PathItem.extend(/** @lends Path# */{
return offset;
}
return null;
- }
+ },
/**
* Calculates the point on the path at the given offset.
@@ -2123,6 +2123,42 @@ var Path = PathItem.extend(/** @lends Path# */{
* the beginning of the path and {@link Path#length} at the end
* @return {Number} the normal vector at the given offset
*/
+
+ /**
+ * Calculates path offsets where the path is tangential to provided tangent.
+ * Note that tangent at start or end are included.
+ * Tangent at segment point is returned even if only one of its handles is
+ * collinear with the provided tangent.
+ *
+ * @param {Point} tangent the tangent to which the path must be tangential
+ * @return {Number[]} path offsets where the path is tangential to the
+ * provided tangent
+ */
+ getOffsetsWithTangent: function(/* tangent */) {
+ var tangent = Point.read(arguments);
+ if (tangent.isZero()) {
+ return [];
+ }
+
+ var offsets = [];
+ var offsetBeforeCurve = 0;
+ var curves = this.getCurves();
+ for (var i = 0; i < curves.length; i++) {
+ var curve = curves[i];
+ // Calculate curves times at vector tangent...
+ var curveTimes = curve.getTimesWithTangent(tangent);
+ for (var j = 0; j < curveTimes.length; j++) {
+ // ...and convert them to path offsets...
+ var offset = offsetBeforeCurve + curve.getOffsetAtTime(curveTimes[j]);
+ // ...avoiding duplicates.
+ if (offsets.indexOf(offset) < 0) {
+ offsets.push(offset);
+ }
+ }
+ offsetBeforeCurve += curve.length;
+ }
+ return offsets;
+ }
}),
new function() { // Scope for drawing
diff --git a/test/tests/Curve.js b/test/tests/Curve.js
index 341fd219..6a4b6c4a 100644
--- a/test/tests/Curve.js
+++ b/test/tests/Curve.js
@@ -347,3 +347,25 @@ test('Curve#divideAt(offset)', function() {
return new Curve(point1, point2).divideAtTime(0.5).point1;
}, middle);
});
+
+test('Curve#getTimesWithTangent()', function() {
+ var curve = new Curve([0, 0], [100, 0], [0, -100], [200, 200]);
+ equals(curve.getTimesWithTangent(), [], 'should return empty array when called without argument');
+ equals(curve.getTimesWithTangent([1, 0]), [0], 'should return tangent at start');
+ equals(curve.getTimesWithTangent([-1, 0]), [0], 'should return the same when called with opposite direction vector');
+ equals(curve.getTimesWithTangent([0, 1]), [1], 'should return tangent at end');
+ equals(curve.getTimesWithTangent([1, 1]), [0.5], 'should return tangent at middle');
+ equals(curve.getTimesWithTangent([1, -1]), [], 'should return empty array when there is no tangent');
+
+ equals(
+ new Curve([0, 0], [100, 0], [500, -500], [-500, -500]).getTimesWithTangent([1, 0]).length,
+ 2,
+ 'should return 2 values for specific self-intersecting path case'
+ );
+
+ equals(
+ new Curve([0, 0], [100, 0], [0, -100], [0, -100]).getTimesWithTangent([1, 0]).length,
+ 2,
+ 'should return 2 values for specific parabollic path case'
+ );
+});
diff --git a/test/tests/Path.js b/test/tests/Path.js
index f198d17d..d5b8e812 100644
--- a/test/tests/Path.js
+++ b/test/tests/Path.js
@@ -611,6 +611,14 @@ test('Path#arcTo(from, through, to); where from, through and to all share the sa
equals(error != null, true, 'We expect this arcTo() command to throw an error');
});
+test('Path#getOffsetsWithTangent()', function() {
+ var path = new Path.Circle(new Point(0, 0), 50);
+ var length = path.length;
+ equals(path.getOffsetsWithTangent(), [], 'should return empty array when called without argument');
+ equals(path.getOffsetsWithTangent([1, 0]), [0.25 * length, 0.75 * length], 'should not return duplicates when tangent is at segment point');
+ equals(path.getOffsetsWithTangent([1, 1]).length, 2, 'should return 2 values when called on a circle with a diagonal vector');
+});
+
test('Path#add() with a lot of segments (#1493)', function() {
var segments = [];
for (var i = 0; i < 100000; i++) {