mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
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:
parent
71a7cc37e6
commit
9dab662a1f
3 changed files with 188 additions and 242 deletions
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
},
|
||||
}
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue