mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-01 02:38:43 -05:00
Add options object to boolean operations and improve handling of open paths.
This closes #1036, closes #1072, closes #1089 and closes #1121
This commit is contained in:
parent
a29ada8f23
commit
e643338422
5 changed files with 182 additions and 99 deletions
|
@ -30,6 +30,9 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
_serializeFields: {
|
||||
children: []
|
||||
},
|
||||
// Enforce creation of beans, as bean getters have hidden parameters.
|
||||
// See #getPathData() and #getArea below.
|
||||
beans: true,
|
||||
|
||||
/**
|
||||
* Creates a new compound path item and places it in the active layer.
|
||||
|
@ -248,11 +251,11 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
* @bean
|
||||
* @type Number
|
||||
*/
|
||||
getArea: function() {
|
||||
getArea: function(_closed) {
|
||||
var children = this._children,
|
||||
area = 0;
|
||||
for (var i = 0, l = children.length; i < l; i++)
|
||||
area += children[i].getArea();
|
||||
area += children[i].getArea(_closed);
|
||||
return area;
|
||||
},
|
||||
|
||||
|
@ -269,10 +272,7 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
for (var i = 0, l = children.length; i < l; i++)
|
||||
length += children[i].getLength();
|
||||
return length;
|
||||
}
|
||||
}, /** @lends CompoundPath# */{
|
||||
// Enforce bean creation for getPathData(), as it has hidden parameters.
|
||||
beans: true,
|
||||
},
|
||||
|
||||
getPathData: function(_matrix, _precision) {
|
||||
// NOTE: #setPathData() is defined in PathItem.
|
||||
|
@ -285,8 +285,8 @@ var CompoundPath = PathItem.extend(/** @lends CompoundPath# */{
|
|||
? _matrix.appended(mx) : _matrix, _precision));
|
||||
}
|
||||
return paths.join('');
|
||||
}
|
||||
}, /** @lends CompoundPath# */{
|
||||
},
|
||||
|
||||
_hitTestChildren: function _hitTestChildren(point, options, viewMatrix) {
|
||||
return _hitTestChildren.base.call(this, point,
|
||||
// If we're not specifically asked to returns paths through
|
||||
|
|
|
@ -295,8 +295,8 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
}
|
||||
}
|
||||
}, /** @lends Path# */{
|
||||
// Enforce bean creation for getPathData() and getArea(), as they have
|
||||
// hidden parameters.
|
||||
// Enforce creation of beans, as bean getters have hidden parameters.
|
||||
// See #getPathData() and #getArea below.
|
||||
beans: true,
|
||||
|
||||
getPathData: function(_matrix, _precision) {
|
||||
|
@ -829,24 +829,28 @@ var Path = PathItem.extend(/** @lends Path# */{
|
|||
* @type Number
|
||||
*/
|
||||
getArea: function(_closed) {
|
||||
// If the call overrides the 'closed' state, do not cache the result.
|
||||
// This is used in tracePaths().
|
||||
var cached = _closed === undefined,
|
||||
area = this._area;
|
||||
if (!cached || area == null) {
|
||||
// Cache the area for the open path, and the the final curve separately,
|
||||
// so open and closed area can be returned at almost no additional cost.
|
||||
var closed = Base.pick(_closed, this._closed),
|
||||
cached = this._area;
|
||||
if (cached == null) {
|
||||
var segments = this._segments,
|
||||
count = segments.length,
|
||||
closed = cached ? this._closed : _closed,
|
||||
last = count - 1;
|
||||
area = 0;
|
||||
for (var i = 0, l = closed ? count : last; i < l; i++) {
|
||||
area += Curve.getArea(Curve.getValues(
|
||||
segments[i], segments[i < last ? i + 1 : 0]));
|
||||
sum = 0,
|
||||
close = 0;
|
||||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
var next = i + 1,
|
||||
last = next >= l,
|
||||
area = Curve.getArea(Curve.getValues(
|
||||
segments[i], segments[last ? 0 : i + 1]));
|
||||
if (last) {
|
||||
close = area;
|
||||
} else {
|
||||
sum += area;
|
||||
}
|
||||
}
|
||||
if (cached)
|
||||
this._area = area;
|
||||
cached = this._area = [sum, close];
|
||||
}
|
||||
return area;
|
||||
return cached[0] + (closed ? cached[1] : 0);
|
||||
},
|
||||
|
||||
/**
|
||||
|
|
|
@ -50,52 +50,52 @@ PathItem.inject(new function() {
|
|||
* remove empty curves, #resolveCrossings() to resolve self-intersection
|
||||
* make sure all paths have correct winding direction.
|
||||
*/
|
||||
function preparePath(path, closed) {
|
||||
function preparePath(path, resolve) {
|
||||
var res = path.clone(false).reduce({ simplify: true })
|
||||
.transform(null, true, true);
|
||||
if (closed)
|
||||
res.setClosed(true);
|
||||
return closed
|
||||
return resolve
|
||||
? res.resolveCrossings().reorient(res.getFillRule() === 'nonzero')
|
||||
: res;
|
||||
}
|
||||
|
||||
function createResult(ctor, paths, reduce, path1, path2) {
|
||||
function createResult(ctor, paths, reduce, path1, path2, options) {
|
||||
var result = new ctor(Item.NO_INSERT);
|
||||
result.addChildren(paths, true);
|
||||
// See if the item can be reduced to just a simple Path.
|
||||
if (reduce)
|
||||
result = result.reduce({ simplify: true });
|
||||
// Insert the resulting path above whichever of the two paths appear
|
||||
// further up in the stack.
|
||||
result.insertAbove(path2 && path1.isSibling(path2)
|
||||
&& path1.getIndex() < path2.getIndex() ? path2 : path1);
|
||||
if (!(options && options.insert === false)) {
|
||||
// Insert the resulting path above whichever of the two paths appear
|
||||
// further up in the stack.
|
||||
result.insertAbove(path2 && path1.isSibling(path2)
|
||||
&& path1.getIndex() < path2.getIndex() ? path2 : path1);
|
||||
}
|
||||
// Copy over the input path attributes, excluding matrix and we're done.
|
||||
result.copyAttributes(path1, true);
|
||||
return result;
|
||||
}
|
||||
|
||||
function computeBoolean(path1, path2, operation) {
|
||||
// Retrieve the operator lookup table for winding numbers.
|
||||
var operator = operators[operation];
|
||||
function computeBoolean(path1, path2, operation, options) {
|
||||
// Only support subtract and intersect operations when computing stroke
|
||||
// based boolean operations.
|
||||
if (options && options.stroke &&
|
||||
/^(subtract|intersect)$/.test(operation))
|
||||
return computeStrokeBoolean(path1, path2, operation === 'subtract');
|
||||
// We do not modify the operands themselves, but create copies instead,
|
||||
// fas produced by the calls to preparePath().
|
||||
// NOTE: The result paths might not belong to the same type i.e.
|
||||
// subtract(A:Path, B:Path):CompoundPath etc.
|
||||
var _path1 = preparePath(path1, true),
|
||||
_path2 = path2 && path1 !== path2 && preparePath(path2, true),
|
||||
// Retrieve the operator lookup table for winding numbers.
|
||||
operator = operators[operation];
|
||||
// Add a simple boolean property to check for a given operation,
|
||||
// e.g. `if (operator.unite)`
|
||||
operator[operation] = true;
|
||||
// If path1 is open, delegate to computeOpenBoolean().
|
||||
// NOTE: Do not access private _closed property here, since path1 may
|
||||
// be a CompoundPath.
|
||||
if (!path1.isClosed())
|
||||
return computeOpenBoolean(path1, path2, operator);
|
||||
// We do not modify the operands themselves, but create copies instead,
|
||||
// fas produced by the calls to preparePath().
|
||||
// Note that the result paths might not belong to the same type
|
||||
// i.e. subtraction(A:Path, B:Path):CompoundPath etc.
|
||||
var _path1 = preparePath(path1, true),
|
||||
_path2 = path2 && path1 !== path2 && preparePath(path2, true);
|
||||
// Give both paths the same orientation except for subtraction
|
||||
// and exclusion, where we need them at opposite orientation.
|
||||
if (_path2 && (operator.subtract || operator.exclude)
|
||||
^ (_path2.isClockwise() ^ _path1.isClockwise()))
|
||||
^ (_path2.isClockwise(true) ^ _path1.isClockwise(true)))
|
||||
_path2.reverse();
|
||||
// Split curves at crossings on both paths. Note that for self-
|
||||
// intersection, path2 is null and getIntersections() handles it.
|
||||
|
@ -183,26 +183,20 @@ PathItem.inject(new function() {
|
|||
paths = tracePaths(segments, operator);
|
||||
}
|
||||
|
||||
return createResult(CompoundPath, paths, true, path1, path2);
|
||||
return createResult(CompoundPath, paths, true, path1, path2, options);
|
||||
}
|
||||
|
||||
function computeOpenBoolean(path1, path2, operator) {
|
||||
// Only support subtract and intersect operations between an open
|
||||
// and a closed path.
|
||||
if (!path2 || !operator.subtract && !operator.intersect) {
|
||||
throw new Error('Boolean operations on open paths only support ' +
|
||||
'subtraction and intersection with another path.');
|
||||
}
|
||||
var _path1 = preparePath(path1, false),
|
||||
_path2 = preparePath(path2, false),
|
||||
function computeStrokeBoolean(path1, path2, subtract) {
|
||||
var _path1 = preparePath(path1),
|
||||
_path2 = preparePath(path2),
|
||||
crossings = _path1.getCrossings(_path2),
|
||||
sub = operator.subtract,
|
||||
paths = [];
|
||||
|
||||
function addPath(path) {
|
||||
// Simple see if the point halfway across the open path is inside
|
||||
// path2, and include / exclude the path based on the operator.
|
||||
if (_path2.contains(path.getPointAt(path.getLength() / 2)) ^ sub) {
|
||||
if (_path2.contains(path.getPointAt(path.getLength() / 2))
|
||||
^ subtract) {
|
||||
paths.unshift(path);
|
||||
return true;
|
||||
}
|
||||
|
@ -358,13 +352,17 @@ PathItem.inject(new function() {
|
|||
* {@link CompoundPath#getCurves()}
|
||||
* @param {Number} [dir=0] the direction in which to determine the
|
||||
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
||||
* @param {Boolean} [closed=false] determines how areas should be closed
|
||||
* when a curve is part of an open path, `false`: area is closed with a
|
||||
* straight line, `true`: area is closed taking the handles of the first
|
||||
* and last segment into account
|
||||
* @param {Boolean} [dontFlip=false] controls whether the algorithm is
|
||||
* allowed to flip direction if it is deemed to produce better results
|
||||
* @return {Object} an object containing the calculated winding number, as
|
||||
* well as an indication whether the point was situated on the contour
|
||||
* @private
|
||||
*/
|
||||
function getWinding(point, curves, dir, dontFlip) {
|
||||
function getWinding(point, curves, dir, closed, dontFlip) {
|
||||
var epsilon = /*#=*/Numerical.WINDING_EPSILON,
|
||||
// Determine the index of the abscissa and ordinate values in the
|
||||
// curve values arrays, based on the direction:
|
||||
|
@ -465,7 +463,7 @@ PathItem.inject(new function() {
|
|||
// again with flipped direction and return that result instead.
|
||||
return !dontFlip && a > paL && a < paR
|
||||
&& Curve.getTangent(v, t)[dir ? 'x' : 'y'] === 0
|
||||
&& getWinding(point, curves, dir ? 0 : 1, true);
|
||||
&& getWinding(point, curves, dir ? 0 : 1, closed, true);
|
||||
}
|
||||
|
||||
function handleCurve(v) {
|
||||
|
@ -505,14 +503,22 @@ PathItem.inject(new function() {
|
|||
// We're on a new (sub-)path, so we need to determine values of
|
||||
// the last non-horizontal curve on this path.
|
||||
vPrev = null;
|
||||
// If the path is not closed, connect the end points with a
|
||||
// straight curve, just like how filling open paths works.
|
||||
// If the path is not closed, connect the first and last segment
|
||||
// based on the value of `closed`:
|
||||
// - `false`: Connect with a straight curve, just like how
|
||||
// filling open paths works.
|
||||
// - `true`: Connect with a curve that takes the segment handles
|
||||
// into account, just like how closed paths behave.
|
||||
if (!path._closed) {
|
||||
var p1 = path.getLastCurve().getPoint2(),
|
||||
p2 = curve.getPoint1(),
|
||||
var s1 = path.getLastCurve().getSegment2(),
|
||||
s2 = curve.getSegment1(),
|
||||
p1 = s1._point,
|
||||
p2 = s2._point,
|
||||
x1 = p1._x, y1 = p1._y,
|
||||
x2 = p2._x, y2 = p2._y;
|
||||
vClose = [x1, y1, x1, y1, x2, y2, x2, y2];
|
||||
vClose = closed
|
||||
? Curve.getValues(s1, s2)
|
||||
: [x1, y1, x1, y1, x2, y2, x2, y2];
|
||||
// This closing curve is a potential candidate for the last
|
||||
// non-horizontal curve.
|
||||
if (vClose[io] !== vClose[io + 6]) {
|
||||
|
@ -555,7 +561,7 @@ PathItem.inject(new function() {
|
|||
// for clockwise and [-1,+1] for counter-clockwise paths.
|
||||
// If the ray is cast in y direction (dir == 1), the
|
||||
// windings always have opposite sign.
|
||||
var add = path.isClockwise() ^ dir ? 1 : -1;
|
||||
var add = path.isClockwise(closed) ^ dir ? 1 : -1;
|
||||
windingL += add;
|
||||
windingR -= add;
|
||||
onPathWinding += add;
|
||||
|
@ -626,10 +632,12 @@ PathItem.inject(new function() {
|
|||
// contributing to the second operand and is outside the
|
||||
// first operand.
|
||||
winding = !(operator.subtract && path2 && (
|
||||
path === path1 && path2._getWinding(pt, dir).winding ||
|
||||
path === path2 && !path1._getWinding(pt, dir).winding))
|
||||
? getWinding(pt, curves, dir)
|
||||
: { winding: 0 };
|
||||
path === path1 &&
|
||||
path2._getWinding(pt, dir, true).winding ||
|
||||
path === path2 &&
|
||||
!path1._getWinding(pt, dir, true).winding))
|
||||
? getWinding(pt, curves, dir, true)
|
||||
: { winding: 0 };
|
||||
break;
|
||||
}
|
||||
length -= curveLength;
|
||||
|
@ -734,6 +742,7 @@ PathItem.inject(new function() {
|
|||
for (var i = 0, l = segments.length; i < l; i++) {
|
||||
var path = null,
|
||||
finished = false,
|
||||
closed = true,
|
||||
seg = segments[i],
|
||||
inter = seg._intersection,
|
||||
handleIn;
|
||||
|
@ -793,18 +802,21 @@ PathItem.inject(new function() {
|
|||
seg = other;
|
||||
}
|
||||
}
|
||||
// Bail out if we're done, or if we encounter an already visited
|
||||
// next segment.
|
||||
if (finished || seg._visited) {
|
||||
// It doesn't hurt to set again to share some code.
|
||||
if (finished) {
|
||||
seg._visited = true;
|
||||
// If we end up on the first or last segment of an operand,
|
||||
// copy over its closed state, to support mixed open/closed
|
||||
// scenarios as described in #1036
|
||||
if (seg.isFirst() || seg.isLast())
|
||||
closed = seg._path._closed;
|
||||
break;
|
||||
}
|
||||
// If there are only valid overlaps and we encounter and invalid
|
||||
// segment, bail out immediately. Otherwise we need to be more
|
||||
// tolerant due to complex situations of crossing,
|
||||
// see findBestIntersection()
|
||||
if (seg._path._validOverlapsOnly && !isValid(seg))
|
||||
// If a visited or invalid segment is encountered, bail out
|
||||
// immediately. But if there aren't only valid overlaps, be more
|
||||
// tolerant due to complex crossing situations.
|
||||
// See findBestIntersection()
|
||||
if (seg._visited
|
||||
|| seg._path._validOverlapsOnly && !isValid(seg))
|
||||
break;
|
||||
if (!path) {
|
||||
path = new Path(Item.NO_INSERT);
|
||||
|
@ -830,7 +842,7 @@ PathItem.inject(new function() {
|
|||
// Finish with closing the paths, and carrying over the last
|
||||
// handleIn to the first segment.
|
||||
path.firstSegment.setHandleIn(handleIn);
|
||||
path.setClosed(true);
|
||||
path.setClosed(closed);
|
||||
} else if (path) {
|
||||
// Only complain about open paths if they would actually contain
|
||||
// an area when closed. Open paths that can silently discarded
|
||||
|
@ -873,39 +885,58 @@ PathItem.inject(new function() {
|
|||
* winding contribution, `0`: in x-direction, `1`: in y-direction
|
||||
* @return {Number} the winding number
|
||||
*/
|
||||
_getWinding: function(point, dir) {
|
||||
return getWinding(point, this.getCurves(), dir);
|
||||
_getWinding: function(point, dir, closed) {
|
||||
return getWinding(point, this.getCurves(), dir, closed);
|
||||
},
|
||||
|
||||
/**
|
||||
* {@grouptitle Boolean Path Operations}
|
||||
*
|
||||
* Merges the geometry of the specified path with this path's geometry
|
||||
* Unites the geometry of the specified path with this path's geometry
|
||||
* and returns the result as a new path item.
|
||||
*
|
||||
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||
* should be inserted back into the scene graph, above both paths
|
||||
* involved in the operation
|
||||
*
|
||||
* @param {PathItem} path the path to unite with
|
||||
* @param {Object} [options] the boolean operation options
|
||||
* @return {PathItem} the resulting path item
|
||||
*/
|
||||
unite: function(path) {
|
||||
return computeBoolean(this, path, 'unite');
|
||||
unite: function(path, options) {
|
||||
return computeBoolean(this, path, 'unite', options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Intersects the geometry of the specified path with this path's
|
||||
* geometry and returns the result as a new path item.
|
||||
*
|
||||
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||
* should be inserted back into the scene graph, above both paths
|
||||
* involved in the operation
|
||||
* @option [options.stroke=false] {Boolean} whether the operation should
|
||||
* be performed on the stroke or on the fill of the first path
|
||||
*
|
||||
* @param {PathItem} path the path to intersect with
|
||||
* @param {Object} [options] the boolean operation options
|
||||
* @return {PathItem} the resulting path item
|
||||
*/
|
||||
intersect: function(path) {
|
||||
return computeBoolean(this, path, 'intersect');
|
||||
intersect: function(path, options) {
|
||||
return computeBoolean(this, path, 'intersect', options);
|
||||
},
|
||||
|
||||
/**
|
||||
* Subtracts the geometry of the specified path from this path's
|
||||
* geometry and returns the result as a new path item.
|
||||
*
|
||||
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||
* should be inserted back into the scene graph, above both paths
|
||||
* involved in the operation
|
||||
* @option [options.stroke=false] {Boolean} whether the operation should
|
||||
* be performed on the stroke or on the fill of the first path
|
||||
*
|
||||
* @param {PathItem} path the path to subtract
|
||||
* @param {Object} [options] the boolean operation options
|
||||
* @return {PathItem} the resulting path item
|
||||
*/
|
||||
subtract: function(path) {
|
||||
|
@ -916,11 +947,16 @@ PathItem.inject(new function() {
|
|||
* Excludes the intersection of the geometry of the specified path with
|
||||
* this path's geometry and returns the result as a new path item.
|
||||
*
|
||||
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||
* should be inserted back into the scene graph, above both paths
|
||||
* involved in the operation
|
||||
*
|
||||
* @param {PathItem} path the path to exclude the intersection of
|
||||
* @param {Object} [options] the boolean operation options
|
||||
* @return {PathItem} the resulting group item
|
||||
*/
|
||||
exclude: function(path) {
|
||||
return computeBoolean(this, path, 'exclude');
|
||||
exclude: function(path, options) {
|
||||
return computeBoolean(this, path, 'exclude', options);
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -929,12 +965,21 @@ PathItem.inject(new function() {
|
|||
* calling {@link #subtract(path)} and {@link #subtract(path)} and
|
||||
* putting the results into a new group.
|
||||
*
|
||||
* @option [options.insert=true] {Boolean} whether the resulting item
|
||||
* should be inserted back into the scene graph, above both paths
|
||||
* involved in the operation
|
||||
* @option [options.stroke=false] {Boolean} whether the operation should
|
||||
* be performed on the stroke or on the fill of the first path
|
||||
*
|
||||
* @param {PathItem} path the path to divide by
|
||||
* @param {Object} [options] the boolean operation options
|
||||
* @return {Group} the resulting group item
|
||||
*/
|
||||
divide: function(path) {
|
||||
return createResult(Group, [this.subtract(path),
|
||||
this.intersect(path)], true, this, path);
|
||||
divide: function(path, options) {
|
||||
return createResult(Group, [
|
||||
this.subtract(path, options),
|
||||
this.intersect(path, options)
|
||||
], true, this, path, options);
|
||||
},
|
||||
|
||||
/*
|
||||
|
|
|
@ -23,6 +23,9 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
_class: 'PathItem',
|
||||
_selectBounds: false,
|
||||
_canScaleStroke: true,
|
||||
// Enforce creation of beans, as bean getters have hidden parameters.
|
||||
// See #isClockwise() below.
|
||||
beans: true,
|
||||
|
||||
initialize: function PathItem() {
|
||||
// Do nothing.
|
||||
|
@ -101,8 +104,8 @@ var PathItem = Item.extend(/** @lends PathItem# */{
|
|||
* @see Path#getArea()
|
||||
* @see CompoundPath#getArea()
|
||||
*/
|
||||
isClockwise: function() {
|
||||
return this.getArea() >= 0;
|
||||
isClockwise: function(_closed) {
|
||||
return this.getArea(_closed) >= 0;
|
||||
},
|
||||
|
||||
setClockwise: function(clockwise) {
|
||||
|
|
|
@ -99,7 +99,7 @@ test('#719', function() {
|
|||
compareBoolean(result, expected);
|
||||
});
|
||||
|
||||
test('#757 (support for open paths)', function() {
|
||||
test('#757 (path1.intersect(pat2, { stroke: true }))', function() {
|
||||
var rect = new Path.Rectangle({
|
||||
from: [100, 250],
|
||||
to: [350, 350]
|
||||
|
@ -116,7 +116,7 @@ test('#757 (support for open paths)', function() {
|
|||
]
|
||||
});
|
||||
|
||||
var res = line.intersect(rect);
|
||||
var res = line.intersect(rect, { stroke: true });
|
||||
|
||||
var children = res.removeChildren();
|
||||
var first = children[0];
|
||||
|
@ -540,6 +540,37 @@ test('#973', function() {
|
|||
'children orientation after calling path.resolveCrossings()');
|
||||
});
|
||||
|
||||
test('#1036', function() {
|
||||
var line1 = new Path([
|
||||
[[305.10732,101.34786],[0,0],[62.9214,0]],
|
||||
[[499.20274,169.42611],[-29.38716,-68.57004],[5.78922,13.50818]],
|
||||
[[497.75426,221.57115],[2.90601,-13.5614],[-9.75434,45.52027]],
|
||||
[[416.63976,331.65512],[31.9259,-30.40562],[-21.77284,20.73604]],
|
||||
[[350.00999,391.04252],[23.92578,-18.22917],[-23.33885,17.78198]],
|
||||
[[277.58633,431.59977],[27.45996,-10.67887],[-1.72805,0.67202]],
|
||||
[[251.51381,437.39367],[0,5.7145],[0,0]]
|
||||
]);
|
||||
|
||||
var line2 = new Path([
|
||||
[[547.00236,88.31161],[0,0],[-1.36563,0]],
|
||||
[[544.10541,85.41466],[1.29555,0.43185],[-9.83725,-3.27908]],
|
||||
[[509.34205,82.51771],[10.32634,-1.29079],[-10.20055,1.27507]],
|
||||
[[444.16075,97.00245],[7.10741,-4.06138],[-4.93514,2.82008]],
|
||||
[[431.12449,105.69328],[4.27047,-2.13524],[-14.94798,7.47399]],
|
||||
[[407.94892,175.22],[1.27008,-13.33587],[-3.16966,33.28138]],
|
||||
[[399.25808,279.51008],[-5.61644,-33.69865],[1.73417,10.40499]],
|
||||
[[415.19129,307.03107],[-5.98792,-8.16534],[2.74694,3.74583]],
|
||||
[[432.57297,328.75817],[-3.89061,-3.29206],[2.9716,2.51443]],
|
||||
[[442.71228,334.55206],[-3.01275,-2.46498],[2.39275,1.95771]],
|
||||
[[448.50617,341.79443],[-2.37502,-1.97918],[39.75954,33.13295]],
|
||||
[[578.86877,378.00626],[-51.65429,10.33086],[11.28627,-2.25725]],
|
||||
[[612.18365,366.41848],[-10.6547,4.26188],[3.10697,-1.24279]],
|
||||
[[617.97755,362.07306],[-3.19904,0],[0,0]]
|
||||
]);
|
||||
compareBoolean(function() { return line1.intersect(line2); },
|
||||
'M424.54226,112.15158c32.89387,9.15202 61.28089,26.0555 74.66048,57.27453c5.78922,13.50818 1.45753,38.58364 -1.44848,52.14504c-8.75233,40.8442 -41.40003,72.54068 -71.07836,100.58668c-4.48065,-5.55963 -9.68976,-12.67924 -11.48461,-15.12676c-5.98792,-8.16534 -14.19904,-17.116 -15.93321,-27.52099c-5.61644,-33.69865 5.52118,-71.0087 8.69084,-104.29008c1.06648,-11.19805 6.14308,-47.34273 16.59334,-63.06842z');
|
||||
});
|
||||
|
||||
test('#1054', function() {
|
||||
var p1 = new Path({
|
||||
segments: [
|
||||
|
|
Loading…
Reference in a new issue