Clean-up various Segment and Curve tests.

Moving functionality back to Path#toShape() since it was too specific, and missleading as part of the exposed Segment API.
This commit is contained in:
Jürg Lehni 2015-09-06 17:27:33 +02:00
parent 71a7cc37e6
commit 9dab662a1f
3 changed files with 188 additions and 242 deletions

View file

@ -133,6 +133,45 @@ var Curve = Base.extend(/** @lends Curve# */{
this._length = this._bounds = undefined;
},
/**
* Returns a copy of the curve.
*
* @return {Curve}
*/
clone: function() {
return new Curve(this._segment1, this._segment2);
},
/**
* @return {String} a string representation of the curve
*/
toString: function() {
var parts = [ 'point1: ' + this._segment1._point ];
if (!this._segment1._handleOut.isZero())
parts.push('handle1: ' + this._segment1._handleOut);
if (!this._segment2._handleIn.isZero())
parts.push('handle2: ' + this._segment2._handleIn);
parts.push('point2: ' + this._segment2._point);
return '{ ' + parts.join(', ') + ' }';
},
/**
* Removes the curve from the path that it belongs to, by removing its
* second segment and merging its handle with the first segment.
* @return {Boolean} {@true if the curve was removed}
*/
remove: function() {
var removed = false;
if (this._path) {
var segment2 = this._segment2,
handleOut = segment2._handleOut;
removed = segment2.remove();
if (removed)
this._segment1._handleOut.set(handleOut.x, handleOut.y);
}
return removed;
},
/**
* The first anchor point of the curve.
*
@ -322,7 +361,7 @@ var Curve = Base.extend(/** @lends Curve# */{
if (this._length == null) {
// Use simple point distance for straight curves
this._length = this.isLinear()
? this._segment2._point.getDistance(this._segment1._point)
? this.getVector().getLength()
: Curve.getLength(this.getValues(), 0, 1);
}
return this._length;
@ -338,6 +377,17 @@ var Curve = Base.extend(/** @lends Curve# */{
return Curve.getArea(this.getValues());
},
/**
* The total direction of the curve as a vector pointing from
* {@link #point1} to {@link #point2}.
*
* @type Point
* @bean
*/
getVector: function() {
return this._segment2._point.subtract(this._segment1._point);
},
getPart: function(from, to) {
return new Curve(Curve.getPart(this.getValues(), from, to));
},
@ -347,67 +397,6 @@ var Curve = Base.extend(/** @lends Curve# */{
return Curve.getLength(this.getValues(), from, to);
},
/**
* Checks if this curve has any curve handles set.
*
* @return {Boolean} {@true if the curve has handles set}
* @see Curve#getHandle1()
* @see Curve#getHandle2()
* @see Segment#hasHandles()
* @see Path#hasHandles()
*/
hasHandles: function() {
return !this._segment1._handleOut.isZero()
|| !this._segment2._handleIn.isZero();
},
/**
* Clears the curve's handles by setting their coordinates to zero,
* turning the curve into a straight line.
*/
clearHandles: function() {
this._segment1._handleOut.set(0, 0);
this._segment2._handleIn.set(0, 0);
},
/**
* Checks if this curve appears as a straight line. This can mean that it
* has no handles defined, or that the handles run collinear with the line
* that connects the curve's start and end point, not falling outside of
* the line.
*
* @return {Boolean} {@true if the curve is linear}
* @see Segment#isLinear()
* @see Path#isLinear()
*/
isLinear: function() {
return Segment.isLinear(this._segment1, this._segment2);
},
/**
* Checks if the the two curves describe lines that are collinear, meaning
* they run in parallel.
*
* @param {Curve} curve the other curve to check against
* @return {Boolean} {@true if the two lines are collinear}
* @see Segment#isCollinear(segment)
*/
isCollinear: function(curve) {
return Segment.isCollinear(this._segment1, this._segment2,
curve._segment1, curve._segment2);
},
/**
* Checks if the curve describes an orthogonal arc, as used in the
* construction of circles and ellipses.
*
* @return {Boolean} {@true if the curve describes an orthogonal arc}
* @see Segment#isOrthogonalArc()
*/
isOrthogonalArc: function() {
return Segment.isOrthogonalArc(this._segment1, this._segment2);
},
/**
* Returns all intersections between two {@link Curve} objects as an array
* of {@link CurveLocation} objects.
@ -541,45 +530,14 @@ var Curve = Base.extend(/** @lends Curve# */{
},
/**
* Removes the curve from the path that it belongs to, by removing its
* second segment and merging its handle with the first segment.
* @return {Boolean} {@true if the curve was removed}
* Clears the curve's handles by setting their coordinates to zero,
* turning the curve into a straight line.
*/
remove: function() {
var removed = false;
if (this._path) {
var segment2 = this._segment2,
handleOut = segment2._handleOut;
removed = segment2.remove();
if (removed)
this._segment1._handleOut.set(handleOut.x, handleOut.y);
}
return removed;
clearHandles: function() {
this._segment1._handleOut.set(0, 0);
this._segment2._handleIn.set(0, 0);
},
/**
* Returns a copy of the curve.
*
* @return {Curve}
*/
clone: function() {
return new Curve(this._segment1, this._segment2);
},
/**
* @return {String} a string representation of the curve
*/
toString: function() {
var parts = [ 'point1: ' + this._segment1._point ];
if (!this._segment1._handleOut.isZero())
parts.push('handle1: ' + this._segment1._handleOut);
if (!this._segment2._handleIn.isZero())
parts.push('handle2: ' + this._segment2._handleIn);
parts.push('point2: ' + this._segment2._point);
return '{ ' + parts.join(', ') + ' }';
},
// Mess with indentation in order to get more line-space below...
statics: {
getValues: function(segment1, segment2, matrix) {
var p1 = segment1._point,
@ -694,26 +652,6 @@ statics: {
&& isZero(v[4] - v[6]) && isZero(v[5] - v[7]));
},
isLinear: function(v) {
// See Segment#isLinear():
var p1x = v[0], p1y = v[1],
p2x = v[6], p2y = v[7],
l = new Point(p2x - p1x, p2y - p1y),
h1 = new Point(v[2] - p1x, v[3] - p1y),
h2 = new Point(v[4] - p2x, v[5] - p2y);
if (l.isZero()) {
return h1.isZero() && h2.isZero();
} else if (h1.isCollinear(l) && h2.isCollinear(l)) {
// Get the scalar projection of h1 and h2 onto l, and make sure they
// lie within l (note that h2 is reversed)
var div = l.dot(l),
p1 = l.dot(h1) / div,
p2 = l.dot(h2) / div;
return p1 >= 0 && p1 <= 1 && p2 <= 0 && p2 >= -1;
}
return false;
},
isFlatEnough: function(v, tolerance) {
// Thanks to Kaspar Fischer and Roger Willcocks for the following:
// http://hcklbrrfnn.files.wordpress.com/2012/08/bez.pdf
@ -833,6 +771,8 @@ statics: {
},
/** @lends Curve# */{
/**
* {@grouptitle Bounding Boxes}
*
* The bounding rectangle of the curve excluding stroke width.
*
* @name Curve#bounds
@ -861,13 +801,88 @@ statics: {
* @type Rectangle
* @ignore
*/
}), /** @lends Curve# */{
}), new function() { // Injection scope for tests
function isLinear(l, h1, h2) {
if (h1.isZero() && h2.isZero()) {
// No handles.
return true;
} else if (l.isZero()) {
// Zero-length line, with some handles defined.
return false;
} else if (h1.isCollinear(l) && h2.isCollinear(l)) {
// Collinear handles. Project them onto line to see if they are
// within the line's range:
var div = l.dot(l),
p1 = l.dot(h1) / div,
p2 = l.dot(h2) / div;
return p1 >= 0 && p1 <= 1 && p2 <= 0 && p2 >= -1;
}
return false;
}
return /** @lends Curve# */{
/**
* {@grouptitle Tests}
*
* Checks if this curve has any curve handles set.
*
* @return {Boolean} {@true if the curve has handles set}
* @see Curve#handle1
* @see Curve#handle2
* @see Segment#hasHandles()
* @see Path#hasHandles()
*/
hasHandles: function() {
return !this._segment1._handleOut.isZero()
|| !this._segment2._handleIn.isZero();
},
/**
* Checks if this curve appears as a straight line. This can mean that
* it has no handles defined, or that the handles run collinear with the
* line that connects the curve's start and end point, not falling
* outside of the line.
*
* @return {Boolean} {@true if the curve is linear}
*/
isLinear: function() {
var seg1 = this._segment1,
seg2 = this._segment2;
return isLinear(seg2._point.subtract(seg1._point),
seg1._handleOut, seg2._handleIn);
},
/**
* Checks if the the two curves describe straight lines that are
* collinear, meaning they run in parallel.
*
* @param {Curve} curve the other curve to check against
* @return {Boolean} {@true if the two lines are collinear}
*/
isCollinear: function(curve) {
return this.isLinear() && curve.isLinear()
&& this.getVector().isCollinear(curve.getVector());
},
statics: {
isLinear: function(v) {
var p1x = v[0], p1y = v[1],
p2x = v[6], p2y = v[7];
return isLinear(new Point(p2x - p1x, p2y - p1y),
new Point(v[2] - p1x, v[3] - p1y),
new Point(v[4] - p2x, v[5] - p2y));
}
}
}
}, /** @lends Curve# */{
// Explicitly deactivate the creation of beans, as we have functions here
// that look like bean getters but actually read arguments.
// See #getParameterOf(), #getLocationOf(), #getNearestLocation(), ...
beans: false,
/**
* {@grouptitle Positions on Curves}
*
* Calculates the curve time parameter of the specified offset on the path,
* relative to the provided start parameter. If offset is a negative value,
* the parameter is searched to the left of the start parameter. If no start
@ -935,6 +950,14 @@ statics: {
return loc ? loc.getOffset() : null;
},
/**
* Returns the nearest location on the curve to the specified point.
*
* @function
* @param {Point} point the point for which we search the nearest location
* @return {CurveLocation} the location on the curve that's the closest to
* the specified point
*/
getNearestLocation: function(/* point */) {
var point = Point.read(arguments),
values = this.getValues(),
@ -967,6 +990,14 @@ statics: {
point.getDistance(pt));
},
/**
* Returns the nearest point on the curve to the specified point.
*
* @function
* @param {Point} point the point for which we search the nearest point
* @return {Point} the point on the curve that's the closest to the
* specified point
*/
getNearestPoint: function(/* point */) {
return this.getNearestLocation.apply(this, arguments).getPoint();
}
@ -1050,7 +1081,6 @@ statics: {
*/
},
new function() { // // Scope to inject various curve evaluation methods
var methods = ['getPoint', 'getTangent', 'getNormal', 'getWeightedTangent',
'getWeightedNormal', 'getCurvature'];
return Base.each(methods,
@ -1183,8 +1213,7 @@ new function() { // Scope for methods that require private functions
return type === 2 ? new Point(y, -x) : new Point(x, y);
}
return {
statics: true,
return { statics: {
getLength: function(v, a, b) {
if (a === undefined)
@ -1274,9 +1303,10 @@ new function() { // Scope for methods that require private functions
getCurvature: function(v, t) {
return evaluate(v, t, 3, false).x;
}
};
}};
},
new function() { // Scope for intersection using bezier fat-line clipping
function addLocation(locations, include, curve1, t1, point1, curve2, t2,
point2) {
var loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2);

View file

@ -1406,15 +1406,47 @@ var Path = PathItem.extend(/** @lends Path# */{
topCenter;
function isCollinear(i, j) {
return segments[i].isCollinear(segments[j]);
var seg1 = segments[i],
seg2 = seg1.getNext(),
seg3 = segments[j],
seg4 = seg3.getNext();
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
&& seg3._handleOut.isZero() && seg4._handleIn.isZero()
&& seg2._point.subtract(seg1._point).isCollinear(
seg4._point.subtract(seg3._point));
}
function isOrthogonal(i) {
return segments[i].isOrthogonal();
var seg2 = segments[i],
seg1 = seg2.getPrevious(),
seg3 = seg2.getNext();
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
&& seg2._handleOut.isZero() && seg3._handleIn.isZero()
&& seg2._point.subtract(seg1._point).isOrthogonal(
seg3._point.subtract(seg2._point));
}
function isArc(i) {
return segments[i].isOrthogonalArc();
var seg1 = segments[i],
seg2 = seg1.getNext(),
handle1 = seg1._handleOut,
handle2 = seg2._handleIn,
kappa = /*#=*/Numerical.KAPPA;
// Look at handle length and the distance to the imaginary corner
// point and see if it their relation is kappa.
if (handle1.isOrthogonal(handle2)) {
var pt1 = seg1._point,
pt2 = seg2._point,
// Find the corner point by intersecting the lines described
// by both handles:
corner = new Line(pt1, handle1, true).intersect(
new Line(pt2, handle2, true), true);
return corner && Numerical.isZero(handle1.getLength() /
corner.subtract(pt1).getLength() - kappa)
&& Numerical.isZero(handle2.getLength() /
corner.subtract(pt2).getLength() - kappa);
}
return false;
}
function getDistance(i, j) {

View file

@ -251,62 +251,6 @@ var Segment = Base.extend(/** @lends Segment# */{
this._handleOut.set(0, 0);
},
/**
* Checks if the curve that starts in this segment appears as a straight
* line. This can mean that it has no handles defined, or that the handles
* run collinear with the line that connects the curve's start and end
* point, not falling outside of the line.
*
* @return {Boolean} {@true if the curve starting in this segment is linear}
* @see Curve#isLinear()
* @see Path#isLinear()
*/
isLinear: function() {
return Segment.isLinear(this, this.getNext());
},
/**
* Checks if the the two segments are the beginning of two lines that are
* collinear, meaning they run in parallel.
*
* @param {Segment} segment the other segment to check against
* @return {Boolean} {@true if the two lines are collinear}
* @see Curve#isCollinear(curve)
*/
isCollinear: function(segment) {
return Segment.isCollinear(this, this.getNext(),
segment, segment.getNext());
},
// TODO: Remove version with typo after a while (deprecated June 2015)
isColinear: '#isCollinear',
/**
* Checks if the segment is connecting two lines that are orthogonal,
* meaning they connect at an 90° angle.
*
* @return {Boolean} {@true if the two lines connected by this segment are
* orthogonal}
*/
isOrthogonal: function() {
return Segment.isOrthogonal(this.getPrevious(), this, this.getNext());
},
/**
* Checks if the segment is the beginning of an orthogonal arc, as used in
* the construction of circles and ellipses.
*
* @return {Boolean} {@true if the segment is the beginning of an orthogonal
* arc}
* @see Curve#isOrthogonalArc()
*/
isOrthogonalArc: function() {
return Segment.isOrthogonalArc(this, this.getNext());
},
// TODO: Remove a while (deprecated August 2015)
isArc: '#isOrthogonalArc',
_selectionState: 0,
/**
@ -561,65 +505,5 @@ var Segment = Base.extend(/** @lends Segment# */{
}
}
return coords;
},
statics: {
// These statics are shared between Segment and Curve, for versions of
// these methods that are implemented in both places. Most of these
// methods relate more to the nature of curves than segments, but since
// curves are made out of segments, and segments are the main path data
// structure, while curves are 2nd class citizens, they are defined here
isLinear: function(seg1, seg2) {
var l = seg2._point.subtract(seg1._point),
h1 = seg1._handleOut,
h2 = seg2._handleIn;
if (l.isZero()) {
return h1.isZero() && h2.isZero();
} else if (h1.isCollinear(l) && h2.isCollinear(l)) {
var div = l.dot(l),
p1 = l.dot(h1) / div,
p2 = l.dot(h2) / div;
return p1 >= 0 && p1 <= 1 && p2 <= 0 && p2 >= -1;
}
return false;
},
isCollinear: function(seg1, seg2, seg3, seg4) {
// TODO: This assumes !hasHandles(), while isLinear() allows handles!
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
&& seg3._handleOut.isZero() && seg4._handleIn.isZero()
&& seg2._point.subtract(seg1._point).isCollinear(
seg4._point.subtract(seg3._point));
},
isOrthogonal: function(seg1, seg2, seg3) {
// TODO: This assumes !hasHandles(), while isLinear() allows handles!
return seg1._handleOut.isZero() && seg2._handleIn.isZero()
&& seg2._handleOut.isZero() && seg3._handleIn.isZero()
&& seg2._point.subtract(seg1._point).isOrthogonal(
seg3._point.subtract(seg2._point));
},
isOrthogonalArc: function(seg1, seg2) {
var handle1 = seg1._handleOut,
handle2 = seg2._handleIn,
kappa = /*#=*/Numerical.KAPPA;
// Look at handle length and the distance to the imaginary corner
// point and see if it their relation is kappa.
if (handle1.isOrthogonal(handle2)) {
var pt1 = seg1._point,
pt2 = seg2._point,
// Find the corner point by intersecting the lines described
// by both handles:
corner = new Line(pt1, handle1, true).intersect(
new Line(pt2, handle2, true), true);
return corner && Numerical.isZero(handle1.getLength() /
corner.subtract(pt1).getLength() - kappa)
&& Numerical.isZero(handle2.getLength() /
corner.subtract(pt2).getLength() - kappa);
}
return false;
},
}
});