diff -r ebdd1d769158 -r eb4f4eceada0 server/php/basic/public_html/static/lib/paper/paper-full.js --- a/server/php/basic/public_html/static/lib/paper/paper-full.js Fri Mar 18 14:15:23 2016 +0100 +++ b/server/php/basic/public_html/static/lib/paper/paper-full.js Thu Mar 31 17:12:38 2016 +0200 @@ -1,5 +1,5 @@ /*! - * Paper.js v0.9.24 - The Swiss Army Knife of Vector Graphics Scripting. + * Paper.js v0.9.25 - The Swiss Army Knife of Vector Graphics Scripting. * http://paperjs.org/ * * Copyright (c) 2011 - 2014, Juerg Lehni & Jonathan Puckey @@ -9,7 +9,7 @@ * * All rights reserved. * - * Date: Fri Aug 21 16:39:41 2015 +0200 + * Date: Sun Oct 25 11:23:38 2015 +0100 * *** * @@ -581,7 +581,7 @@ handlers = handlers[type] = handlers[type] || []; if (handlers.indexOf(func) === -1) { handlers.push(func); - if (entry && entry.install && handlers.length == 1) + if (entry && entry.install && handlers.length === 1) entry.install.call(this, type); } } @@ -746,7 +746,7 @@ } }, - version: '0.9.24', + version: "0.9.25", getView: function() { return this.project && this.project.getView(); @@ -929,18 +929,26 @@ var abs = Math.abs, sqrt = Math.sqrt, pow = Math.pow, - TOLERANCE = 1e-6, EPSILON = 1e-12, MACHINE_EPSILON = 1.12e-16; + function clip(value, min, max) { + return value < min ? min : value > max ? max : value; + } + return { - TOLERANCE: TOLERANCE, + TOLERANCE: 1e-6, EPSILON: EPSILON, MACHINE_EPSILON: MACHINE_EPSILON, + CURVETIME_EPSILON: 4e-7, + GEOMETRIC_EPSILON: 2e-7, + WINDING_EPSILON: 2e-7, + TRIGONOMETRIC_EPSILON: 1e-7, + CLIPPING_EPSILON: 1e-7, KAPPA: 4 * (sqrt(2) - 1) / 3, isZero: function(val) { - return abs(val) <= EPSILON; + return val >= -EPSILON && val <= EPSILON; }, integrate: function(f, a, b, n) { @@ -978,16 +986,18 @@ solveQuadratic: function(a, b, c, roots, min, max) { var count = 0, + eMin = min - EPSILON, + eMax = max + EPSILON, x1, x2 = Infinity, B = b, D; - b /= 2; + b /= -2; D = b * b - a * c; if (D !== 0 && abs(D) < MACHINE_EPSILON) { var gmC = pow(abs(a * b * c), 1 / 3); if (gmC < 1e-8) { - var mult = pow(10, abs( - Math.floor(Math.log(gmC) * Math.LOG10E))); + var mult = pow(10, + abs(Math.floor(Math.log(gmC) * Math.LOG10E))); if (!isFinite(mult)) mult = 0; a *= mult; @@ -1000,25 +1010,22 @@ if (abs(B) < EPSILON) return abs(c) < EPSILON ? -1 : 0; x1 = -c / B; - } else { - if (D >= -MACHINE_EPSILON) { - D = D < 0 ? 0 : D; - var R = sqrt(D); - if (b >= MACHINE_EPSILON && b <= MACHINE_EPSILON) { - x1 = abs(a) >= abs(c) ? R / a : -c / R; - x2 = -x1; - } else { - var q = -(b + (b < 0 ? -1 : 1) * R); - x1 = q / a; - x2 = c / q; - } - } - } - if (isFinite(x1) && (min == null || x1 >= min && x1 <= max)) - roots[count++] = x1; + } else if (D >= -MACHINE_EPSILON) { + var Q = D < 0 ? 0 : sqrt(D), + R = b + (b < 0 ? -Q : Q); + if (R === 0) { + x1 = c / a; + x2 = -x1; + } else { + x1 = R / a; + x2 = c / R; + } + } + if (isFinite(x1) && (min == null || x1 > eMin && x1 < eMax)) + roots[count++] = min == null ? x1 : clip(x1, min, max); if (x2 !== x1 - && isFinite(x2) && (min == null || x2 >= min && x2 <= max)) - roots[count++] = x2; + && isFinite(x2) && (min == null || x2 > eMin && x2 < eMax)) + roots[count++] = min == null ? x2 : clip(x2, min, max); return count; }, @@ -1071,8 +1078,8 @@ } var count = Numerical.solveQuadratic(a, b1, c2, roots, min, max); if (isFinite(x) && (count === 0 || x !== roots[count - 1]) - && (min == null || x >= min && x <= max)) - roots[count++] = x; + && (min == null || x > min - EPSILON && x < max + EPSILON)) + roots[count++] = min == null ? x : clip(x, min, max); return count; } }; @@ -1259,11 +1266,11 @@ return this.clone(); angle = angle * Math.PI / 180; var point = center ? this.subtract(center) : this, - s = Math.sin(angle), - c = Math.cos(angle); + sin = Math.sin(angle), + cos = Math.cos(angle); point = new Point( - point.x * c - point.y * s, - point.x * s + point.y * c + point.x * cos - point.y * sin, + point.x * sin + point.y * cos ); return center ? point.add(center) : point; }, @@ -1305,18 +1312,22 @@ return Rectangle.read(arguments).contains(this); }, - isClose: function(point, tolerance) { + isClose: function() { + var point = Point.read(arguments), + tolerance = Base.read(arguments); return this.getDistance(point) < tolerance; }, - isCollinear: function(point) { - return Math.abs(this.cross(point)) < 0.000001; + isCollinear: function() { + var point = Point.read(arguments); + return Point.isCollinear(this.x, this.y, point.x, point.y); }, isColinear: '#isCollinear', - isOrthogonal: function(point) { - return Math.abs(this.dot(point)) < 0.000001; + isOrthogonal: function() { + var point = Point.read(arguments); + return Point.isOrthogonal(this.x, this.y, point.x, point.y); }, isZero: function() { @@ -1338,16 +1349,12 @@ }, project: function() { - var point = Point.read(arguments); - if (point.isZero()) { - return new Point(0, 0); - } else { - var scale = this.dot(point) / point.dot(point); - return new Point( - point.x * scale, - point.y * scale - ); - } + var point = Point.read(arguments), + scale = point.isZero() ? 0 : this.dot(point) / point.dot(point); + return new Point( + point.x * scale, + point.y * scale + ); }, statics: { @@ -1371,6 +1378,18 @@ random: function() { return new Point(Math.random(), Math.random()); + }, + + isCollinear: function(x1, y1, x2, y2) { + return Math.abs(x1 * y2 - y1 * x2) + <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) + * 1e-7; + }, + + isOrthogonal: function(x1, y1, x2, y2) { + return Math.abs(x1 * x2 + y1 * y2) + <= Math.sqrt((x1 * x1 + y1 * y1) * (x2 * x2 + y2 * y2)) + * 1e-7; } } }, Base.each(['round', 'ceil', 'floor', 'abs'], function(name) { @@ -1922,7 +1941,8 @@ this._owner[this._setter](this); return this; } -}, new function() { +}, +new function() { var proto = Rectangle.prototype; return Base.each(['x', 'y', 'width', 'height'], function(key) { @@ -2255,7 +2275,7 @@ _transformBounds: function(bounds, dest, _dontNotify) { var coords = this._transformCorners(bounds), min = coords.slice(0, 2), - max = coords.slice(); + max = min.slice(); for (var i = 2; i < 8; i++) { var val = coords[i], j = i & 1; @@ -2417,10 +2437,10 @@ true, isInfinite); }, - getSide: function(point) { + getSide: function(point, isInfinite) { return Line.getSide( this._px, this._py, this._vx, this._vy, - point.x, point.y, true); + point.x, point.y, true, isInfinite); }, getDistance: function(point) { @@ -2429,29 +2449,45 @@ point.x, point.y, true)); }, + isCollinear: function(line) { + return Point.isCollinear(this._vx, this._vy, line._vx, line._vy); + }, + + isOrthogonal: function(line) { + return Point.isOrthogonal(this._vx, this._vy, line._vx, line._vy); + }, + statics: { - intersect: function(apx, apy, avx, avy, bpx, bpy, bvx, bvy, asVector, + intersect: function(p1x, p1y, v1x, v1y, p2x, p2y, v2x, v2y, asVector, isInfinite) { if (!asVector) { - avx -= apx; - avy -= apy; - bvx -= bpx; - bvy -= bpy; - } - var cross = avx * bvy - avy * bvx; + v1x -= p1x; + v1y -= p1y; + v2x -= p2x; + v2y -= p2y; + } + var cross = v1x * v2y - v1y * v2x; if (!Numerical.isZero(cross)) { - var dx = apx - bpx, - dy = apy - bpy, - ta = (bvx * dy - bvy * dx) / cross, - tb = (avx * dy - avy * dx) / cross; - if (isInfinite || 0 <= ta && ta <= 1 && 0 <= tb && tb <= 1) + var dx = p1x - p2x, + dy = p1y - p2y, + u1 = (v2x * dy - v2y * dx) / cross, + u2 = (v1x * dy - v1y * dx) / cross, + epsilon = 1e-12, + uMin = -epsilon, + uMax = 1 + epsilon; + if (isInfinite + || uMin < u1 && u1 < uMax && uMin < u2 && u2 < uMax) { + if (!isInfinite) { + u1 = u1 <= 0 ? 0 : u1 >= 1 ? 1 : u1; + } return new Point( - apx + ta * avx, - apy + ta * avy); - } - }, - - getSide: function(px, py, vx, vy, x, y, asVector) { + p1x + u1 * v1x, + p1y + u1 * v1y); + } + } + }, + + getSide: function(px, py, vx, vy, x, y, asVector, isInfinite) { if (!asVector) { vx -= px; vy -= py; @@ -2459,15 +2495,10 @@ var v2x = x - px, v2y = y - py, ccw = v2x * vy - v2y * vx; - if (ccw === 0) { - ccw = v2x * vx + v2y * vy; - if (ccw > 0) { - v2x -= vx; - v2y -= vy; - ccw = v2x * vx + v2y * vy; - if (ccw < 0) - ccw = 0; - } + if (ccw === 0 && !isInfinite) { + ccw = (v2x * vx + v2x * vx) / (vx * vx + vy * vy); + if (ccw >= 0 && ccw <= 1) + ccw = 0; } return ccw < 0 ? -1 : ccw > 0 ? 1 : 0; }, @@ -2477,11 +2508,9 @@ vx -= px; vy -= py; } - return Numerical.isZero(vx) - ? vy >= 0 ? px - x : x - px - : Numerical.isZero(vy) - ? vx >= 0 ? y - py : py - y - : (vx * (y - py) - vy * (x - px)) / Math.sqrt(vx * vx + vy * vy); + return vx === 0 ? vy > 0 ? x - px : px - x + : vy === 0 ? vx < 0 ? y - py : py - y + : ((x-px) * vy - (y-py) * vx) / Math.sqrt(vx * vx + vy * vy); } } }); @@ -2783,70 +2812,32 @@ return hasProps; }, - _events: new function() { - - var mouseFlags = { - mousedown: { - mousedown: 1, - mousedrag: 1, - click: 1, - doubleclick: 1 - }, - mouseup: { - mouseup: 1, - mousedrag: 1, - click: 1, - doubleclick: 1 - }, - mousemove: { - mousedrag: 1, - mousemove: 1, - mouseenter: 1, - mouseleave: 1 - } - }; - - var mouseEvent = { - install: function(type) { - var counters = this.getView()._eventCounters; - if (counters) { - for (var key in mouseFlags) { - counters[key] = (counters[key] || 0) - + (mouseFlags[key][type] || 0); - } + _events: Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick', + 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'], + function(name) { + this[name] = { + install: function(type) { + this.getView()._installEvent(type); + }, + + uninstall: function(type) { + this.getView()._uninstallEvent(type); + } + }; + }, { + onFrame: { + install: function() { + this.getView()._animateItem(this, true); + }, + + uninstall: function() { + this.getView()._animateItem(this, false); } }, - uninstall: function(type) { - var counters = this.getView()._eventCounters; - if (counters) { - for (var key in mouseFlags) - counters[key] -= mouseFlags[key][type] || 0; - } - } - }; - - return Base.each(['onMouseDown', 'onMouseUp', 'onMouseDrag', 'onClick', - 'onDoubleClick', 'onMouseMove', 'onMouseEnter', 'onMouseLeave'], - function(name) { - this[name] = mouseEvent; - }, { - onFrame: { - install: function() { - this._animateItem(true); - }, - uninstall: function() { - this._animateItem(false); - } - }, - - onLoad: {} - } - ); - }, - - _animateItem: function(animate) { - this.getView()._animateItem(this, animate); - }, + + onLoad: {} + } + ), _serialize: function(options, dictionary) { var props = {}, @@ -3452,8 +3443,8 @@ intersects: function(item, _matrix) { if (!(item instanceof Item)) return false; - return this._asPathItem().getIntersections(item._asPathItem(), - _matrix || item._matrix).length > 0; + return this._asPathItem().getIntersections(item._asPathItem(), null, + _matrix || item._matrix, true).length > 0; }, hitTest: function() { @@ -3476,7 +3467,7 @@ tolerancePadding = options._tolerancePadding = new Size( Path._getPenPadding(1, totalMatrix.inverted()) ).multiply( - Math.max(options.tolerance, 0.000001) + Math.max(options.tolerance, 1e-6) ); point = matrix._inverseTransform(point); @@ -3599,7 +3590,7 @@ var overlapping = match.overlapping, inside = match.inside, bounds = overlapping || inside, - rect = bounds && Rectangle.read([bounds]); + rect = bounds && Rectangle.read([bounds]); param = { items: [], inside: !!inside, @@ -3903,6 +3894,10 @@ return item ? item.isDescendant(this) : false; }, + isSibling: function(item) { + return this._parent === item._parent; + }, + isGroupedWith: function(item) { var parent = this._parent; while (parent) { @@ -4178,7 +4173,7 @@ if (this._drawSelected) this._drawSelected(ctx, mx, selectedItems); if (this._boundsSelected) { - var half = size / 2; + var half = size / 2, coords = mx._transformCorners(this.getInternalBounds()); ctx.beginPath(); for (var i = 0; i < 8; i++) @@ -4557,7 +4552,6 @@ } }, new function() { - function getCornerCenter(that, point, expand) { var radius = that._radius; if (!radius.isZero()) { @@ -4902,7 +4896,7 @@ if (/^data:/.test(src)) return src; var canvas = this.getCanvas(); - return canvas ? canvas.toDataURL() : null; + return canvas ? canvas.toDataURL.apply(canvas, arguments) : null; }, drawImage: function(image ) { @@ -5133,7 +5127,7 @@ point, handleIn, handleOut; if (count === 0) { } else if (count === 1) { - if (arg0.point) { + if ('point' in arg0) { point = arg0.point; handleIn = arg0.handleIn; handleOut = arg0.handleOut; @@ -5157,8 +5151,9 @@ }, _serialize: function(options) { - return Base.serialize(this.isStraight() ? this._point - : [this._point, this._handleIn, this._handleOut], + return Base.serialize(this.hasHandles() + ? [this._point, this._handleIn, this._handleOut] + : this._point, options, true); }, @@ -5209,33 +5204,13 @@ }, hasHandles: function() { - return !this.isStraight(); - }, - - isStraight: function() { - return this._handleIn.isZero() && this._handleOut.isZero(); - }, - - isLinear: function() { - return Segment.isLinear(this, this.getNext()); - }, - - isCollinear: function(segment) { - return Segment.isCollinear(this, this.getNext(), - segment, segment.getNext()); - }, - - isColinear: '#isCollinear', - - isOrthogonal: function() { - return Segment.isOrthogonal(this.getPrevious(), this, this.getNext()); - }, - - isOrthogonalArc: function() { - return Segment.isOrthogonalArc(this, this.getNext()); - }, - - isArc: '#isOrthogonalArc', + return !this._handleIn.isZero() || !this._handleOut.isZero(); + }, + + clearHandles: function() { + this._handleIn.set(0, 0); + this._handleOut.set(0, 0); + }, _selectionState: 0, @@ -5309,7 +5284,25 @@ || this._path._closed && segments[segments.length - 1]) || null; }, + isFirst: function() { + return this._index === 0; + }, + + isLast: function() { + var path = this._path; + return path && this._index === path._segments.length - 1 || false; + }, + reverse: function() { + var handleIn = this._handleIn, + handleOut = this._handleOut, + inX = handleIn._x, + inY = handleIn._y; + handleIn.set(handleOut._x, handleOut._y); + handleOut.set(inX, inY); + }, + + reversed: function() { return new Segment(this._point, this._handleOut, this._handleIn); }, @@ -5390,46 +5383,6 @@ } } return coords; - }, - - statics: { - - isLinear: function(seg1, seg2) { - var l = seg2._point.subtract(seg1._point); - return l.isCollinear(seg1._handleOut) - && l.isCollinear(seg2._handleIn); - }, - - isCollinear: function(seg1, seg2, seg3, seg4) { - 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) { - 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 = 0.5522847498307936; - if (handle1.isOrthogonal(handle2)) { - var pt1 = seg1._point, - pt2 = seg2._point, - 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; - }, } }); @@ -5508,42 +5461,88 @@ _class: 'Curve', initialize: function Curve(arg0, arg1, arg2, arg3, arg4, arg5, arg6, arg7) { - var count = arguments.length; + var count = arguments.length, + seg1, seg2, + point1, point2, + handle1, handle2; if (count === 3) { this._path = arg0; - this._segment1 = arg1; - this._segment2 = arg2; + seg1 = arg1; + seg2 = arg2; } else if (count === 0) { - this._segment1 = new Segment(); - this._segment2 = new Segment(); + seg1 = new Segment(); + seg2 = new Segment(); } else if (count === 1) { - this._segment1 = new Segment(arg0.segment1); - this._segment2 = new Segment(arg0.segment2); + if ('segment1' in arg0) { + seg1 = new Segment(arg0.segment1); + seg2 = new Segment(arg0.segment2); + } else if ('point1' in arg0) { + point1 = arg0.point1; + handle1 = arg0.handle1; + handle2 = arg0.handle2; + point2 = arg0.point2; + } else if (Array.isArray(arg0)) { + point1 = [arg0[0], arg0[1]]; + point2 = [arg0[6], arg0[7]]; + handle1 = [arg0[2] - arg0[0], arg0[3] - arg0[1]]; + handle2 = [arg0[4] - arg0[6], arg0[5] - arg0[7]]; + } } else if (count === 2) { - this._segment1 = new Segment(arg0); - this._segment2 = new Segment(arg1); - } else { - var point1, handle1, handle2, point2; - if (count === 4) { - point1 = arg0; - handle1 = arg1; - handle2 = arg2; - point2 = arg3; - } else if (count === 8) { - point1 = [arg0, arg1]; - point2 = [arg6, arg7]; - handle1 = [arg2 - arg0, arg3 - arg1]; - handle2 = [arg4 - arg6, arg5 - arg7]; - } - this._segment1 = new Segment(point1, null, handle1); - this._segment2 = new Segment(point2, handle2, null); - } + seg1 = new Segment(arg0); + seg2 = new Segment(arg1); + } else if (count === 4) { + point1 = arg0; + handle1 = arg1; + handle2 = arg2; + point2 = arg3; + } else if (count === 8) { + point1 = [arg0, arg1]; + point2 = [arg6, arg7]; + handle1 = [arg2 - arg0, arg3 - arg1]; + handle2 = [arg4 - arg6, arg5 - arg7]; + } + this._segment1 = seg1 || new Segment(point1, null, handle1); + this._segment2 = seg2 || new Segment(point2, handle2, null); + }, + + _serialize: function(options) { + return Base.serialize(this.hasHandles() + ? [this.getPoint1(), this.getHandle1(), this.getHandle2(), + this.getPoint2()] + : [this.getPoint1(), this.getPoint2()], + options, true); }, _changed: function() { this._length = this._bounds = undefined; }, + clone: function() { + return new Curve(this._segment1, this._segment2); + }, + + 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(', ') + ' }'; + }, + + 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; + }, + getPoint1: function() { return this._segment1._point; }, @@ -5608,6 +5607,16 @@ || this._path._closed && curves[curves.length - 1]) || null; }, + isFirst: function() { + return this._segment1._index === 0; + }, + + isLast: function() { + var path = this._path; + return path && this._segment1._index === path._curves.length - 1 + || false; + }, + isSelected: function() { return this.getPoint1().isSelected() && this.getHandle2().isSelected() @@ -5635,11 +5644,8 @@ }, getLength: function() { - if (this._length == null) { - this._length = this.isLinear() - ? this._segment2._point.getDistance(this._segment1._point) - : Curve.getLength(this.getValues(), 0, 1); - } + if (this._length == null) + this._length = Curve.getLength(this.getValues(), 0, 1); return this._length; }, @@ -5647,6 +5653,10 @@ return Curve.getArea(this.getValues()); }, + getLine: function() { + return new Line(this._segment1._point, this._segment2._point); + }, + getPart: function(from, to) { return new Curve(Curve.getPart(this.getValues(), from, to)); }, @@ -5655,27 +5665,10 @@ return Curve.getLength(this.getValues(), from, to); }, - hasHandles: function() { - return !this._segment1._handleOut.isZero() - || !this._segment2._handleIn.isZero(); - }, - - isLinear: function() { - return Segment.isLinear(this._segment1, this._segment2); - }, - - isCollinear: function(curve) { - return Ssegment.isCollinear(this._segment1, this._segment2, - curve._segment1, curve._segment2); - }, - - isOrthogonalArc: function() { - return Segment.isOrthogonalArc(this._segment1, this._segment2); - }, - getIntersections: function(curve) { - return Curve.filterIntersections(Curve.getIntersections( - this.getValues(), curve.getValues(), this, curve, [])); + return Curve._getIntersections(this.getValues(), + curve && curve !== this ? curve.getValues() : null, + this, curve, [], {}); }, _getParameter: function(offset, isParameter) { @@ -5688,39 +5681,35 @@ : this.getParameterAt(offset, 0); }, - divide: function(offset, isParameter, ignoreLinear) { + divide: function(offset, isParameter, _setHandles) { var parameter = this._getParameter(offset, isParameter), - tolerance = 0.000001, + tMin = 4e-7, + tMax = 1 - tMin, res = null; - if (parameter > tolerance && parameter < 1 - tolerance) { + if (parameter >= tMin && parameter <= tMax) { var parts = Curve.subdivide(this.getValues(), parameter), - isLinear = ignoreLinear ? false : this.isLinear(), left = parts[0], - right = parts[1]; - - if (!isLinear) { - this._segment1._handleOut.set(left[2] - left[0], + right = parts[1], + setHandles = _setHandles || this.hasHandles(), + segment1 = this._segment1, + segment2 = this._segment2, + path = this._path; + if (setHandles) { + segment1._handleOut.set(left[2] - left[0], left[3] - left[1]); - this._segment2._handleIn.set(right[4] - right[6], + segment2._handleIn.set(right[4] - right[6], right[5] - right[7]); } - var x = left[6], y = left[7], segment = new Segment(new Point(x, y), - !isLinear && new Point(left[4] - x, left[5] - y), - !isLinear && new Point(right[2] - x, right[3] - y)); - - if (this._path) { - if (this._segment1._index > 0 && this._segment2._index === 0) { - this._path.add(segment); - } else { - this._path.insert(this._segment2._index, segment); - } - res = this; + setHandles && new Point(left[4] - x, left[5] - y), + setHandles && new Point(right[2] - x, right[3] - y)); + if (path) { + path.insert(segment1._index + 1, segment); + res = this.getNext(); } else { - var end = this._segment2; this._segment2 = segment; - res = new Curve(segment, end); + res = new Curve(segment, segment2); } } return res; @@ -5733,34 +5722,13 @@ : null; }, - reverse: function() { - return new Curve(this._segment2.reverse(), this._segment1.reverse()); - }, - - 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; - }, - - clone: function() { - return new Curve(this._segment1, this._segment2); - }, - - 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(', ') + ' }'; + reversed: function() { + return new Curve(this._segment2.reversed(), this._segment1.reversed()); + }, + + clearHandles: function() { + this._segment1._handleOut.set(0, 0); + this._segment2._handleIn.set(0, 0); }, statics: { @@ -5811,43 +5779,86 @@ return Numerical.solveCubic(a, b, c, p1 - val, roots, min, max); }, - getParameterOf: function(v, x, y) { - var tolerance = 0.000001; - if (Math.abs(v[0] - x) < tolerance && Math.abs(v[1] - y) < tolerance) - return 0; - if (Math.abs(v[6] - x) < tolerance && Math.abs(v[7] - y) < tolerance) - return 1; - var txs = [], - tys = [], - sx = Curve.solveCubic(v, 0, x, txs, 0, 1), - sy = Curve.solveCubic(v, 1, y, tys, 0, 1), - tx, ty; - for (var cx = 0; sx === -1 || cx < sx;) { - if (sx === -1 || (tx = txs[cx++]) > 0 && tx < 1) { - for (var cy = 0; sy === -1 || cy < sy;) { - if (sy === -1 || (ty = tys[cy++]) > 0 && ty < 1) { - if (sx === -1) { - tx = ty; - } else if (sy === -1) { - ty = tx; - } - if (Math.abs(tx - ty) < tolerance) - return (tx + ty) * 0.5; - } - } - if (sx === -1) - break; - } - } - return null; + getParameterOf: function(v, point) { + var p1 = new Point(v[0], v[1]), + p2 = new Point(v[6], v[7]), + epsilon = 1e-12, + t = point.isClose(p1, epsilon) ? 0 + : point.isClose(p2, epsilon) ? 1 + : null; + if (t !== null) + return t; + var coords = [point.x, point.y], + roots = [], + geomEpsilon = 2e-7; + for (var c = 0; c < 2; c++) { + var count = Curve.solveCubic(v, c, coords[c], roots, 0, 1); + for (var i = 0; i < count; i++) { + t = roots[i]; + if (point.isClose(Curve.getPoint(v, t), geomEpsilon)) + return t; + } + } + return point.isClose(p1, geomEpsilon) ? 0 + : point.isClose(p2, geomEpsilon) ? 1 + : null; + }, + + getNearestParameter: function(v, point) { + if (Curve.isStraight(v)) { + var p1x = v[0], p1y = v[1], + p2x = v[6], p2y = v[7], + vx = p2x - p1x, vy = p2y - p1y, + det = vx * vx + vy * vy; + if (det === 0) + return 0; + var u = ((point.x - p1x) * vx + (point.y - p1y) * vy) / det; + return u < 1e-12 ? 0 + : u > 0.999999999999 ? 1 + : Curve.getParameterOf(v, + new Point(p1x + u * vx, p1y + u * vy)); + } + + var count = 100, + minDist = Infinity, + minT = 0; + + function refine(t) { + if (t >= 0 && t <= 1) { + var dist = point.getDistance(Curve.getPoint(v, t), true); + if (dist < minDist) { + minDist = dist; + minT = t; + return true; + } + } + } + + for (var i = 0; i <= count; i++) + refine(i / count); + + var step = 1 / (count * 2); + while (step > 4e-7) { + if (!refine(minT - step) && !refine(minT + step)) + step /= 2; + } + return minT; }, getPart: function(v, from, to) { + var flip = from > to; + if (flip) { + var tmp = from; + from = to; + to = tmp; + } if (from > 0) v = Curve.subdivide(v, from)[1]; if (to < 1) v = Curve.subdivide(v, (to - from) / (1 - from))[0]; - return v; + return flip + ? [v[6], v[7], v[4], v[5], v[2], v[3], v[0], v[1]] + : v; }, hasHandles: function(v) { @@ -5856,14 +5867,6 @@ && isZero(v[4] - v[6]) && isZero(v[5] - v[7])); }, - isLinear: function(v) { - var p1x = v[0], p1y = v[1], - p2x = v[6], p2y = v[7], - l = new Point(p2x - p1x, p2y - p1y); - return l.isCollinear(new Point(v[2] - p1x, v[3] - p1y)) - && l.isCollinear(new Point(v[4] - p2x, v[5] - p2y)); - }, - isFlatEnough: function(v, tolerance) { var p1x = v[0], p1y = v[1], c1x = v[2], c1y = v[3], @@ -5879,21 +5882,14 @@ getArea: function(v) { var p1x = v[0], p1y = v[1], - c1x = v[2], c1y = v[3], - c2x = v[4], c2y = v[5], - p2x = v[6], p2y = v[7]; - return ( 3.0 * c1y * p1x - 1.5 * c1y * c2x - - 1.5 * c1y * p2x - 3.0 * p1y * c1x - - 1.5 * p1y * c2x - 0.5 * p1y * p2x - + 1.5 * c2y * p1x + 1.5 * c2y * c1x - - 3.0 * c2y * p2x + 0.5 * p2y * p1x - + 1.5 * p2y * c1x + 3.0 * p2y * c2x) / 10; - }, - - getEdgeSum: function(v) { - return (v[0] - v[2]) * (v[3] + v[1]) - + (v[2] - v[4]) * (v[5] + v[3]) - + (v[4] - v[6]) * (v[7] + v[5]); + p2x = v[6], p2y = v[7], + h1x = (v[2] + p1x) / 2, + h1y = (v[3] + p1y) / 2, + h2x = (v[4] + v[6]) / 2, + h2y = (v[5] + v[7]) / 2; + return 6 * ((p1x - h1x) * (h1y + p1y) + + (h1x - h2x) * (h2y + h1y) + + (h2x - p2x) * (p2y + h2y)) / 10; }, getBounds: function(v) { @@ -5919,7 +5915,7 @@ b = 2 * (v0 + v2) - 4 * v1, c = v1 - v0, count = Numerical.solveQuadratic(a, b, c, roots), - tMin = 0.000001, + tMin = 4e-7, tMax = 1 - tMin; add(v3, 0); for (var i = 0; i < count; i++) { @@ -5941,14 +5937,72 @@ this._bounds = {}; var bounds = this._bounds[name]; if (!bounds) { - bounds = this._bounds[name] = Path[name]([this._segment1, - this._segment2], false, this._path.getStyle()); + var path = this._path; + bounds = this._bounds[name] = Path[name]( + [this._segment1, this._segment2], false, + path && path.getStyle()); } return bounds.clone(); }; }, { +}), Base.each({ + isStraight: function(l, h1, h2) { + if (h1.isZero() && h2.isZero()) { + return true; + } else if (l.isZero()) { + return false; + } 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; + }, + + isLinear: function(l, h1, h2) { + var third = l.divide(3); + return h1.equals(third) && h2.negate().equals(third); + } +}, function(test, name) { + this[name] = function() { + var seg1 = this._segment1, + seg2 = this._segment2; + return test(seg2._point.subtract(seg1._point), + seg1._handleOut, seg2._handleIn); + }; + + this.statics[name] = function(v) { + var p1x = v[0], p1y = v[1], + p2x = v[6], p2y = v[7]; + return test(new Point(p2x - p1x, p2y - p1y), + new Point(v[2] - p1x, v[3] - p1y), + new Point(v[4] - p2x, v[5] - p2y)); + }; +}, { + statics: {}, + + hasHandles: function() { + return !this._segment1._handleOut.isZero() + || !this._segment2._handleIn.isZero(); + }, + + isCollinear: function(curve) { + return curve && this.isStraight() && curve.isStraight() + && this.getLine().isCollinear(curve.getLine()); + }, + + isHorizontal: function() { + return this.isStraight() && Math.abs(this.getTangentAt(0.5, true).y) + < 1e-7; + }, + + isVertical: function() { + return this.isStraight() && Math.abs(this.getTangentAt(0.5, true).x) + < 1e-7; + } }), { beans: false, @@ -5957,8 +6011,7 @@ }, getParameterOf: function() { - var point = Point.read(arguments); - return Curve.getParameterOf(this.getValues(), point.x, point.y); + return Curve.getParameterOf(this.getValues(), Point.read(arguments)); }, getLocationAt: function(offset, isParameter) { @@ -5981,32 +6034,9 @@ getNearestLocation: function() { var point = Point.read(arguments), values = this.getValues(), - count = 100, - minDist = Infinity, - minT = 0; - - function refine(t) { - if (t >= 0 && t <= 1) { - var dist = point.getDistance(Curve.getPoint(values, t), true); - if (dist < minDist) { - minDist = dist; - minT = t; - return true; - } - } - } - - for (var i = 0; i <= count; i++) - refine(i / count); - - var step = 1 / (count * 2); - while (step > 0.000001) { - if (!refine(minT - step) && !refine(minT + step)) - step /= 2; - } - var pt = Curve.getPoint(values, minT); - return new CurveLocation(this, minT, pt, null, null, null, - point.getDistance(pt)); + t = Curve.getNearestParameter(values, point), + pt = Curve.getPoint(values, t); + return new CurveLocation(this, t, pt, null, point.getDistance(pt)); }, getNearestPoint: function() { @@ -6064,11 +6094,12 @@ c1x = v[2], c1y = v[3], c2x = v[4], c2y = v[5], p2x = v[6], p2y = v[7], - tolerance = 0.000001, + tMin = 4e-7, + tMax = 1 - tMin, x, y; - if (type === 0 && (t < tolerance || t > 1 - tolerance)) { - var isZero = t < tolerance; + if (type === 0 && (t < tMin || t > tMax)) { + var isZero = t < tMin; x = isZero ? p1x : p2x; y = isZero ? p1y : p2y; } else { @@ -6083,10 +6114,10 @@ x = ((ax * t + bx) * t + cx) * t + p1x; y = ((ay * t + by) * t + cy) * t + p1y; } else { - if (t < tolerance) { + if (t < tMin) { x = cx; y = cy; - } else if (t > 1 - tolerance) { + } else if (t > tMax) { x = 3 * (p2x - c2x); y = 3 * (p2y - c2y); } else { @@ -6094,14 +6125,15 @@ y = (3 * ay * t + 2 * by) * t + cy; } if (normalized) { - if (x === 0 && y === 0 - && (t < tolerance || t > 1 - tolerance)) { + if (x === 0 && y === 0 && (t < tMin || t > tMax)) { x = c2x - c1x; y = c2y - c1y; } var len = Math.sqrt(x * x + y * y); - x /= len; - y /= len; + if (len) { + x /= len; + y /= len; + } } if (type === 3) { var x2 = 6 * ax * t + 2 * bx, @@ -6115,18 +6147,14 @@ return type === 2 ? new Point(y, -x) : new Point(x, y); } - return { - statics: true, + return { statics: { getLength: function(v, a, b) { if (a === undefined) a = 0; if (b === undefined) b = 1; - var isZero = Numerical.isZero; - if (a === 0 && b === 1 - && isZero(v[0] - v[2]) && isZero(v[1] - v[3]) - && isZero(v[6] - v[4]) && isZero(v[7] - v[5])) { + if (a === 0 && b === 1 && Curve.isStraight(v)) { var dx = v[6] - v[0], dy = v[7] - v[1]; return Math.sqrt(dx * dx + dy * dy); @@ -6140,15 +6168,14 @@ start = offset < 0 ? 1 : 0 if (offset === 0) return start; - var tolerance = 0.000001, - abs = Math.abs, + var abs = Math.abs, forward = offset > 0, a = forward ? start : 0, b = forward ? 1 : start, ds = getLengthIntegrand(v), rangeLength = Numerical.integrate(ds, a, b, getIterations(a, b)); - if (abs(offset - rangeLength) < tolerance) { + if (abs(offset - rangeLength) < 1e-12) { return forward ? b : a; } else if (abs(offset) > rangeLength) { return null; @@ -6161,8 +6188,8 @@ start = t; return length - offset; } - return Numerical.findRoot(f, ds, start + guess, a, b, 16, - tolerance); + return Numerical.findRoot(f, ds, start + guess, a, b, 32, + 1e-12); }, getPoint: function(v, t) { @@ -6188,24 +6215,55 @@ getCurvature: function(v, t) { return evaluate(v, t, 3, false).x; } - }; -}, new function() { - function addLocation(locations, include, curve1, t1, point1, curve2, t2, - point2) { - var loc = new CurveLocation(curve1, t1, point1, curve2, t2, point2); - if (!include || include(loc)) - locations.push(loc); - } - - function addCurveIntersections(v1, v2, curve1, curve2, locations, include, + }}; +}, +new function() { + + function addLocation(locations, param, v1, c1, t1, p1, v2, c2, t2, p2, + overlap) { + var startConnected = param.startConnected, + endConnected = param.endConnected, + tMin = 4e-7, + tMax = 1 - tMin; + if (t1 == null) + t1 = Curve.getParameterOf(v1, p1); + if (t1 !== null && t1 >= (startConnected ? tMin : 0) && + t1 <= (endConnected ? tMax : 1)) { + if (t2 == null) + t2 = Curve.getParameterOf(v2, p2); + if (t2 !== null && t2 >= (endConnected ? tMin : 0) && + t2 <= (startConnected ? tMax : 1)) { + var renormalize = param.renormalize; + if (renormalize) { + var res = renormalize(t1, t2); + t1 = res[0]; + t2 = res[1]; + } + var loc1 = new CurveLocation(c1, t1, + p1 || Curve.getPoint(v1, t1), overlap), + loc2 = new CurveLocation(c2, t2, + p2 || Curve.getPoint(v2, t2), overlap), + flip = loc1.getPath() === loc2.getPath() + && loc1.getIndex() > loc2.getIndex(), + loc = flip ? loc2 : loc1, + include = param.include; + loc1._intersection = loc2; + loc2._intersection = loc1; + if (!include || include(loc)) { + CurveLocation.insert(locations, loc, true); + } + } + } + } + + function addCurveIntersections(v1, v2, c1, c2, locations, param, tMin, tMax, uMin, uMax, oldTDiff, reverse, recursion) { - if (recursion > 32) + if (++recursion >= 24) return; var q0x = v2[0], q0y = v2[1], q3x = v2[6], q3y = v2[7], - tolerance = 0.000001, getSignedDistance = Line.getSignedDistance, - d1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]) || 0, - d2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]) || 0, + d1 = getSignedDistance(q0x, q0y, q3x, q3y, v2[2], v2[3]), + d2 = getSignedDistance(q0x, q0y, q3x, q3y, v2[4], v2[5]), factor = d1 * d2 > 0 ? 3 / 4 : 4 / 9, dMin = factor * Math.min(0, d1, d2), dMax = factor * Math.max(0, d1, d2), @@ -6213,61 +6271,51 @@ dp1 = getSignedDistance(q0x, q0y, q3x, q3y, v1[2], v1[3]), dp2 = getSignedDistance(q0x, q0y, q3x, q3y, v1[4], v1[5]), dp3 = getSignedDistance(q0x, q0y, q3x, q3y, v1[6], v1[7]), - tMinNew, tMaxNew, tDiff; - if (q0x === q3x && uMax - uMin < tolerance && recursion > 3) { - tMaxNew = tMinNew = (tMax + tMin) / 2; - tDiff = 0; - } else { - var hull = getConvexHull(dp0, dp1, dp2, dp3), - top = hull[0], - bottom = hull[1], - tMinClip, tMaxClip; - tMinClip = clipConvexHull(top, bottom, dMin, dMax); - top.reverse(); - bottom.reverse(); - tMaxClip = clipConvexHull(top, bottom, dMin, dMax); - if (tMinClip == null || tMaxClip == null) - return; - v1 = Curve.getPart(v1, tMinClip, tMaxClip); - tDiff = tMaxClip - tMinClip; - tMinNew = tMax * tMinClip + tMin * (1 - tMinClip); - tMaxNew = tMax * tMaxClip + tMin * (1 - tMaxClip); - } + hull = getConvexHull(dp0, dp1, dp2, dp3), + top = hull[0], + bottom = hull[1], + tMinClip, + tMaxClip; + if ((tMinClip = clipConvexHull(top, bottom, dMin, dMax)) == null || + (tMaxClip = clipConvexHull(top.reverse(), bottom.reverse(), + dMin, dMax)) == null) + return; + v1 = Curve.getPart(v1, tMinClip, tMaxClip); + var tDiff = tMaxClip - tMinClip, + tMinNew = tMin + (tMax - tMin) * tMinClip, + tMaxNew = tMin + (tMax - tMin) * tMaxClip; if (oldTDiff > 0.5 && tDiff > 0.5) { if (tMaxNew - tMinNew > uMax - uMin) { var parts = Curve.subdivide(v1, 0.5), t = tMinNew + (tMaxNew - tMinNew) / 2; addCurveIntersections( - v2, parts[0], curve2, curve1, locations, include, - uMin, uMax, tMinNew, t, tDiff, !reverse, ++recursion); + v2, parts[0], c2, c1, locations, param, + uMin, uMax, tMinNew, t, tDiff, !reverse, recursion); addCurveIntersections( - v2, parts[1], curve2, curve1, locations, include, + v2, parts[1], c2, c1, locations, param, uMin, uMax, t, tMaxNew, tDiff, !reverse, recursion); } else { var parts = Curve.subdivide(v2, 0.5), t = uMin + (uMax - uMin) / 2; addCurveIntersections( - parts[0], v1, curve2, curve1, locations, include, - uMin, t, tMinNew, tMaxNew, tDiff, !reverse, ++recursion); + parts[0], v1, c2, c1, locations, param, + uMin, t, tMinNew, tMaxNew, tDiff, !reverse, recursion); addCurveIntersections( - parts[1], v1, curve2, curve1, locations, include, + parts[1], v1, c2, c1, locations, param, t, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion); } - } else if (Math.max(uMax - uMin, tMaxNew - tMinNew) < tolerance) { + } else if (Math.max(uMax - uMin, tMaxNew - tMinNew) + < 1e-7) { var t1 = tMinNew + (tMaxNew - tMinNew) / 2, t2 = uMin + (uMax - uMin) / 2; - if (reverse) { - addLocation(locations, include, - curve2, t2, Curve.getPoint(v2, t2), - curve1, t1, Curve.getPoint(v1, t1)); - } else { - addLocation(locations, include, - curve1, t1, Curve.getPoint(v1, t1), - curve2, t2, Curve.getPoint(v2, t2)); - } - } else if (tDiff > 0) { - addCurveIntersections(v2, v1, curve2, curve1, locations, include, - uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, ++recursion); + v1 = c1.getValues(); + v2 = c2.getValues(); + addLocation(locations, param, + reverse ? v2 : v1, reverse ? c2 : c1, reverse ? t2 : t1, null, + reverse ? v1 : v2, reverse ? c1 : c2, reverse ? t1 : t2, null); + } else if (tDiff > 1e-12) { + addCurveIntersections(v2, v1, c2, c1, locations, param, + uMin, uMax, tMinNew, tMaxNew, tDiff, !reverse, recursion); } } @@ -6276,32 +6324,21 @@ p1 = [ 1 / 3, dq1 ], p2 = [ 2 / 3, dq2 ], p3 = [ 1, dq3 ], - getSignedDistance = Line.getSignedDistance, - dist1 = getSignedDistance(0, dq0, 1, dq3, 1 / 3, dq1), - dist2 = getSignedDistance(0, dq0, 1, dq3, 2 / 3, dq2), - flip = false, + dist1 = dq1 - (2 * dq0 + dq3) / 3, + dist2 = dq2 - (dq0 + 2 * dq3) / 3, hull; if (dist1 * dist2 < 0) { hull = [[p0, p1, p3], [p0, p2, p3]]; - flip = dist1 < 0; } else { - var pmax, cross = 0, - distZero = dist1 === 0 || dist2 === 0; - if (Math.abs(dist1) > Math.abs(dist2)) { - pmax = p1; - cross = (dq3 - dq2 - (dq3 - dq0) / 3) - * (2 * (dq3 - dq2) - dq3 + dq1) / 3; - } else { - pmax = p2; - cross = (dq1 - dq0 + (dq0 - dq3) / 3) - * (-2 * (dq0 - dq1) + dq0 - dq2) / 3; - } - hull = cross < 0 || distZero - ? [[p0, pmax, p3], [p0, p3]] - : [[p0, p1, p2, p3], [p0, p3]]; - flip = dist1 ? dist1 < 0 : dist2 < 0; - } - return flip ? hull.reverse() : hull; + var distRatio = dist1 / dist2; + hull = [ + distRatio >= 2 ? [p0, p1, p3] + : distRatio <= .5 ? [p0, p2, p3] + : [p0, p1, p2, p3], + [p0, p3] + ]; + } + return (dist1 || dist2) < 0 ? hull.reverse() : hull; } function clipConvexHull(hullTop, hullBottom, dMin, dMax) { @@ -6320,17 +6357,18 @@ for (var i = 1, l = part.length; i < l; i++) { var qx = part[i][0], qy = part[i][1]; - if (top ? qy >= threshold : qy <= threshold) - return px + (threshold - py) * (qx - px) / (qy - py); + if (top ? qy >= threshold : qy <= threshold) { + return qy === threshold ? qx + : px + (threshold - py) * (qx - px) / (qy - py); + } px = qx; py = qy; } return null; } - function addCurveLineIntersections(v1, v2, curve1, curve2, locations, - include) { - var flip = Curve.isLinear(v1), + function addCurveLineIntersections(v1, v2, c1, c2, locations, param) { + var flip = Curve.isStraight(v1), vc = flip ? v2 : v1, vl = flip ? v1 : v2, lx1 = vl[0], ly1 = vl[1], @@ -6340,113 +6378,221 @@ angle = Math.atan2(-ldy, ldx), sin = Math.sin(angle), cos = Math.cos(angle), - rlx2 = ldx * cos - ldy * sin, - rvl = [0, 0, 0, 0, rlx2, 0, rlx2, 0], rvc = []; for(var i = 0; i < 8; i += 2) { var x = vc[i] - lx1, y = vc[i + 1] - ly1; rvc.push( x * cos - y * sin, - y * cos + x * sin); + x * sin + y * cos); } var roots = [], count = Curve.solveCubic(rvc, 1, 0, roots, 0, 1); for (var i = 0; i < count; i++) { var tc = roots[i], - x = Curve.getPoint(rvc, tc).x; - if (x >= 0 && x <= rlx2) { - var tl = Curve.getParameterOf(rvl, x, 0), + pc = Curve.getPoint(vc, tc), + tl = Curve.getParameterOf(vl, pc); + if (tl !== null) { + var pl = Curve.getPoint(vl, tl), t1 = flip ? tl : tc, t2 = flip ? tc : tl; - addLocation(locations, include, - curve1, t1, Curve.getPoint(v1, t1), - curve2, t2, Curve.getPoint(v2, t2)); - } - } - } - - function addLineIntersection(v1, v2, curve1, curve2, locations, include) { - var point = Line.intersect( + if (!param.endConnected || t2 > Numerical.CURVETIME_EPSILON) { + addLocation(locations, param, + v1, c1, t1, flip ? pl : pc, + v2, c2, t2, flip ? pc : pl); + } + } + } + } + + function addLineIntersection(v1, v2, c1, c2, locations, param) { + var pt = Line.intersect( v1[0], v1[1], v1[6], v1[7], v2[0], v2[1], v2[6], v2[7]); - if (point) { - var x = point.x, - y = point.y; - addLocation(locations, include, - curve1, Curve.getParameterOf(v1, x, y), point, - curve2, Curve.getParameterOf(v2, x, y), point); + if (pt) { + addLocation(locations, param, v1, c1, null, pt, v2, c2, null, pt); } } return { statics: { - getIntersections: function(v1, v2, c1, c2, locations, include) { - var linear1 = Curve.isLinear(v1), - linear2 = Curve.isLinear(v2), - c1p1 = c1.getPoint1(), - c1p2 = c1.getPoint2(), - c2p1 = c2.getPoint1(), - c2p2 = c2.getPoint2(), - tolerance = 0.000001; - if (c1p1.isClose(c2p1, tolerance)) - addLocation(locations, include, c1, 0, c1p1, c2, 0, c1p1); - if (c1p1.isClose(c2p2, tolerance)) - addLocation(locations, include, c1, 0, c1p1, c2, 1, c1p1); - (linear1 && linear2 + _getIntersections: function(v1, v2, c1, c2, locations, param) { + if (!v2) { + return Curve._getSelfIntersection(v1, c1, locations, param); + } + var c1p1x = v1[0], c1p1y = v1[1], + c1p2x = v1[6], c1p2y = v1[7], + c2p1x = v2[0], c2p1y = v2[1], + c2p2x = v2[6], c2p2y = v2[7], + c1s1x = (3 * v1[2] + c1p1x) / 4, + c1s1y = (3 * v1[3] + c1p1y) / 4, + c1s2x = (3 * v1[4] + c1p2x) / 4, + c1s2y = (3 * v1[5] + c1p2y) / 4, + c2s1x = (3 * v2[2] + c2p1x) / 4, + c2s1y = (3 * v2[3] + c2p1y) / 4, + c2s2x = (3 * v2[4] + c2p2x) / 4, + c2s2y = (3 * v2[5] + c2p2y) / 4, + min = Math.min, + max = Math.max; + if (!( max(c1p1x, c1s1x, c1s2x, c1p2x) >= + min(c2p1x, c2s1x, c2s2x, c2p2x) && + min(c1p1x, c1s1x, c1s2x, c1p2x) <= + max(c2p1x, c2s1x, c2s2x, c2p2x) && + max(c1p1y, c1s1y, c1s2y, c1p2y) >= + min(c2p1y, c2s1y, c2s2y, c2p2y) && + min(c1p1y, c1s1y, c1s2y, c1p2y) <= + max(c2p1y, c2s1y, c2s2y, c2p2y))) + return locations; + if (!param.startConnected && !param.endConnected) { + var overlaps = Curve.getOverlaps(v1, v2); + if (overlaps) { + for (var i = 0; i < 2; i++) { + var overlap = overlaps[i]; + addLocation(locations, param, + v1, c1, overlap[0], null, + v2, c2, overlap[1], null, true); + } + return locations; + } + } + + var straight1 = Curve.isStraight(v1), + straight2 = Curve.isStraight(v2), + straight = straight1 && straight2, + epsilon = 1e-12, + before = locations.length; + (straight ? addLineIntersection - : linear1 || linear2 + : straight1 || straight2 ? addCurveLineIntersections : addCurveIntersections)( - v1, v2, c1, c2, locations, include, + v1, v2, c1, c2, locations, param, 0, 1, 0, 1, 0, false, 0); - if (c1p2.isClose(c2p1, tolerance)) - addLocation(locations, include, c1, 1, c1p2, c2, 0, c1p2); - if (c1p2.isClose(c2p2, tolerance)) - addLocation(locations, include, c1, 1, c1p2, c2, 1, c1p2); + if (straight && locations.length > before) + return locations; + var c1p1 = new Point(c1p1x, c1p1y), + c1p2 = new Point(c1p2x, c1p2y), + c2p1 = new Point(c2p1x, c2p1y), + c2p2 = new Point(c2p2x, c2p2y); + if (c1p1.isClose(c2p1, epsilon)) + addLocation(locations, param, v1, c1, 0, c1p1, v2, c2, 0, c2p1); + if (!param.startConnected && c1p1.isClose(c2p2, epsilon)) + addLocation(locations, param, v1, c1, 0, c1p1, v2, c2, 1, c2p2); + if (!param.endConnected && c1p2.isClose(c2p1, epsilon)) + addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 0, c2p1); + if (c1p2.isClose(c2p2, epsilon)) + addLocation(locations, param, v1, c1, 1, c1p2, v2, c2, 1, c2p2); return locations; }, - filterIntersections: function(locations, _expand) { - var last = locations.length - 1, - tMax = 1 - 0.000001; - for (var i = last; i >= 0; i--) { - var loc = locations[i], - next = loc._curve.getNext(), - next2 = loc._curve2.getNext(); - if (next && loc._parameter >= tMax) { - loc._parameter = 0; - loc._curve = next; - } - if (next2 && loc._parameter2 >= tMax) { - loc._parameter2 = 0; - loc._curve2 = next2; - } - } - - function compare(loc1, loc2) { - var path1 = loc1.getPath(), - path2 = loc2.getPath(); - return path1 === path2 - ? (loc1.getIndex() + loc1.getParameter()) - - (loc2.getIndex() + loc2.getParameter()) - : path1._id - path2._id; - } - - if (last > 0) { - locations.sort(compare); - for (var i = last; i > 0; i--) { - if (locations[i].equals(locations[i - 1])) { - locations.splice(i, 1); - last--; + _getSelfIntersection: function(v1, c1, locations, param) { + var p1x = v1[0], p1y = v1[1], + h1x = v1[2], h1y = v1[3], + h2x = v1[4], h2y = v1[5], + p2x = v1[6], p2y = v1[7]; + var line = new Line(p1x, p1y, p2x, p2y, false), + side1 = line.getSide(new Point(h1x, h1y), true), + side2 = line.getSide(new Point(h2x, h2y), true); + if (side1 === side2) { + var edgeSum = (p1x - h2x) * (h1y - p2y) + + (h1x - p2x) * (h2y - p1y); + if (edgeSum * side1 > 0) + return locations; + } + var ax = p2x - 3 * h2x + 3 * h1x - p1x, + bx = h2x - 2 * h1x + p1x, + cx = h1x - p1x, + ay = p2y - 3 * h2y + 3 * h1y - p1y, + by = h2y - 2 * h1y + p1y, + cy = h1y - p1y, + ac = ay * cx - ax * cy, + ab = ay * bx - ax * by, + bc = by * cx - bx * cy; + if (ac * ac - 4 * ab * bc < 0) { + var roots = [], + tSplit, + count = Numerical.solveCubic( + ax * ax + ay * ay, + 3 * (ax * bx + ay * by), + 2 * (bx * bx + by * by) + ax * cx + ay * cy, + bx * cx + by * cy, + roots, 0, 1); + if (count > 0) { + for (var i = 0, maxCurvature = 0; i < count; i++) { + var curvature = Math.abs( + c1.getCurvatureAt(roots[i], true)); + if (curvature > maxCurvature) { + maxCurvature = curvature; + tSplit = roots[i]; + } } - } - } - if (_expand) { - for (var i = last; i >= 0; i--) - locations.push(locations[i].getIntersection()); - locations.sort(compare); + var parts = Curve.subdivide(v1, tSplit); + param.endConnected = true; + param.renormalize = function(t1, t2) { + return [t1 * tSplit, t2 * (1 - tSplit) + tSplit]; + }; + Curve._getIntersections(parts[0], parts[1], c1, c1, + locations, param); + } } return locations; + }, + + getOverlaps: function(v1, v2) { + var abs = Math.abs, + timeEpsilon = 4e-7, + geomEpsilon = 2e-7, + straight1 = Curve.isStraight(v1), + straight2 = Curve.isStraight(v2), + straight = straight1 && straight2; + + function getLineLengthSquared(v) { + var x = v[6] - v[0], + y = v[7] - v[1]; + return x * x + y * y; + } + + if (straight) { + var flip = getLineLengthSquared(v1) < getLineLengthSquared(v2), + l1 = flip ? v2 : v1, + l2 = flip ? v1 : v2, + line = new Line(l1[0], l1[1], l1[6], l1[7]); + if (line.getDistance(new Point(l2[0], l2[1])) > geomEpsilon || + line.getDistance(new Point(l2[6], l2[7])) > geomEpsilon) + return null; + } else if (straight1 ^ straight2) { + return null; + } + + var v = [v1, v2], + pairs = []; + for (var i = 0, t1 = 0; + i < 2 && pairs.length < 2; + i += t1 === 0 ? 0 : 1, t1 = t1 ^ 1) { + var t2 = Curve.getParameterOf(v[i ^ 1], new Point( + v[i][t1 === 0 ? 0 : 6], + v[i][t1 === 0 ? 1 : 7])); + if (t2 != null) { + var pair = i === 0 ? [t1, t2] : [t2, t1]; + if (pairs.length === 0 || + abs(pair[0] - pairs[0][0]) > timeEpsilon && + abs(pair[1] - pairs[0][1]) > timeEpsilon) + pairs.push(pair); + } + if (i === 1 && pairs.length === 0) + break; + } + if (pairs.length !== 2) { + pairs = null; + } else if (!straight) { + var o1 = Curve.getPart(v1, pairs[0][0], pairs[1][0]), + o2 = Curve.getPart(v2, pairs[0][1], pairs[1][1]); + if (abs(o2[2] - o1[2]) > geomEpsilon || + abs(o2[3] - o1[3]) > geomEpsilon || + abs(o2[4] - o1[4]) > geomEpsilon || + abs(o2[5] - o1[5]) > geomEpsilon) + pairs = null; + } + return pairs; } }}; }); @@ -6455,58 +6601,82 @@ _class: 'CurveLocation', beans: true, - initialize: function CurveLocation(curve, parameter, point, _curve2, - _parameter2, _point2, _distance) { + initialize: function CurveLocation(curve, parameter, point, + _overlap, _distance) { + if (parameter > 0.9999996) { + var next = curve.getNext(); + if (next) { + parameter = 0; + curve = next; + } + } this._id = UID.get(CurveLocation); + this._setCurve(curve); + this._parameter = parameter; + this._point = point || curve.getPointAt(parameter, true); + this._overlap = _overlap; + this._distance = _distance; + this._intersection = this._next = this._prev = null; + }, + + _setCurve: function(curve) { var path = curve._path; this._version = path ? path._version : 0; this._curve = curve; - this._parameter = parameter; - this._point = point || curve.getPointAt(parameter, true); - this._curve2 = _curve2; - this._parameter2 = _parameter2; - this._point2 = _point2; - this._distance = _distance; + this._segment = null; this._segment1 = curve._segment1; this._segment2 = curve._segment2; }, - getSegment: function(_preferFirst) { - if (!this._segment) { - var curve = this.getCurve(), - parameter = this.getParameter(); - if (parameter === 1) { - this._segment = curve._segment2; - } else if (parameter === 0 || _preferFirst) { - this._segment = curve._segment1; - } else if (parameter == null) { - return null; - } else { - this._segment = curve.getPartLength(0, parameter) + _setSegment: function(segment) { + this._setCurve(segment.getCurve()); + this._segment = segment; + this._parameter = segment === this._segment1 ? 0 : 1; + this._point = segment._point.clone(); + }, + + getSegment: function() { + var curve = this.getCurve(), + segment = this._segment; + if (!segment) { + var parameter = this.getParameter(); + if (parameter === 0) { + segment = curve._segment1; + } else if (parameter === 1) { + segment = curve._segment2; + } else if (parameter != null) { + segment = curve.getPartLength(0, parameter) < curve.getPartLength(parameter, 1) ? curve._segment1 : curve._segment2; } - } - return this._segment; + this._segment = segment; + } + return segment; }, getCurve: function() { var curve = this._curve, - path = curve && curve._path; + path = curve && curve._path, + that = this; if (path && path._version !== this._version) { - curve = null; - this._parameter = null; - } - if (!curve) { - curve = this._segment1.getCurve(); - if (curve.getParameterOf(this._point) == null) - curve = this._segment2.getPrevious().getCurve(); - this._curve = curve; - path = curve._path; - this._version = path ? path._version : 0; - } - return curve; + curve = this._parameter = this._curve = this._offset = null; + } + + function trySegment(segment) { + var curve = segment && segment.getCurve(); + if (curve && (that._parameter = curve.getParameterOf(that._point)) + != null) { + that._setCurve(curve); + that._segment = segment; + return curve; + } + } + + return curve + || trySegment(this._segment) + || trySegment(this._segment1) + || trySegment(this._segment2.getPrevious()); }, getPath: function() { @@ -6532,8 +6702,19 @@ }, getOffset: function() { - var path = this.getPath(); - return path ? path._getOffset(this) : this.getCurveOffset(); + var offset = this._offset; + if (offset == null) { + offset = 0; + var path = this.getPath(), + index = this.getIndex(); + if (path && index != null) { + var curves = path.getCurves(); + for (var i = 0; i < index; i++) + offset += curves[i].getLength(); + } + this._offset = offset += this.getCurveOffset(); + } + return offset; }, getCurveOffset: function() { @@ -6543,13 +6724,7 @@ }, getIntersection: function() { - var intersection = this._intersection; - if (!intersection && this._curve2) { - this._intersection = intersection = new CurveLocation(this._curve2, - this._parameter2, this._point2 || this._point, this); - intersection._intersection = this; - } - return intersection; + return this._intersection; }, getDistance: function() { @@ -6557,25 +6732,44 @@ }, divide: function() { - var curve = this.getCurve(); - return curve && curve.divide(this.getParameter(), true); + var curve = this.getCurve(), + res = null; + if (curve) { + res = curve.divide(this.getParameter(), true); + if (res) + this._setSegment(res._segment1); + } + return res; }, split: function() { var curve = this.getCurve(); - return curve && curve.split(this.getParameter(), true); - }, - - equals: function(loc) { - var abs = Math.abs, - tolerance = 0.000001; - return this === loc - || loc instanceof CurveLocation - && this.getCurve() === loc.getCurve() - && abs(this.getParameter() - loc.getParameter()) < tolerance - && this._curve2 === loc._curve2 - && abs(this._parameter2 - loc._parameter2) < tolerance - || false; + return curve ? curve.split(this.getParameter(), true) : null; + }, + + equals: function(loc, _ignoreOther) { + var res = this === loc, + epsilon = 2e-7; + if (!res && loc instanceof CurveLocation + && this.getPath() === loc.getPath() + && this.getPoint().isClose(loc.getPoint(), epsilon)) { + var c1 = this.getCurve(), + c2 = loc.getCurve(), + abs = Math.abs, + diff = abs( + ((c1.isLast() && c2.isFirst() ? -1 : c1.getIndex()) + + this.getParameter()) - + ((c2.isLast() && c1.isFirst() ? -1 : c2.getIndex()) + + loc.getParameter())); + res = (diff < 4e-7 + || ((diff = abs(this.getOffset() - loc.getOffset())) < epsilon + || abs(this.getPath().getLength() - diff) < epsilon)) + && (_ignoreOther + || (!this._intersection && !loc._intersection + || this._intersection && this._intersection.equals( + loc._intersection, true))); + } + return res; }, toString: function() { @@ -6593,17 +6787,123 @@ if (this._distance != null) parts.push('distance: ' + f.number(this._distance)); return '{ ' + parts.join(', ') + ' }'; + }, + + isTouching: function() { + var inter = this._intersection; + if (inter && this.getTangent().isCollinear(inter.getTangent())) { + var curve1 = this.getCurve(), + curve2 = inter.getCurve(); + return !(curve1.isStraight() && curve2.isStraight() + && curve1.getLine().intersect(curve2.getLine())); + } + return false; + }, + + isCrossing: function() { + var inter = this._intersection; + if (!inter) + return false; + var t1 = this.getParameter(), + t2 = inter.getParameter(), + tMin = 4e-7, + tMax = 1 - tMin; + if (t1 >= tMin && t1 <= tMax || t2 >= tMin && t2 <= tMax) + return !this.isTouching(); + var c2 = this.getCurve(), + c1 = c2.getPrevious(), + c4 = inter.getCurve(), + c3 = c4.getPrevious(), + PI = Math.PI; + if (!c1 || !c3) + return false; + + function isInRange(angle, min, max) { + return min < max + ? angle > min && angle < max + : angle > min && angle <= PI || angle >= -PI && angle < max; + } + + var a1 = c1.getTangentAt(tMax, true).negate().getAngleInRadians(), + a2 = c2.getTangentAt(tMin, true).getAngleInRadians(), + a3 = c3.getTangentAt(tMax, true).negate().getAngleInRadians(), + a4 = c4.getTangentAt(tMin, true).getAngleInRadians(); + + return (isInRange(a3, a1, a2) ^ isInRange(a4, a1, a2)) + && (isInRange(a3, a2, a1) ^ isInRange(a4, a2, a1)); + }, + + isOverlap: function() { + return !!this._overlap; } }, Base.each(Curve.evaluateMethods, function(name) { - if (name !== 'getPoint') { - var get = name + 'At'; - this[name] = function() { - var parameter = this.getParameter(), - curve = this.getCurve(); - return parameter != null && curve && curve[get](parameter, true); - }; - } -}, {})); + var get = name + 'At'; + this[name] = function() { + var parameter = this.getParameter(), + curve = this.getCurve(); + return parameter != null && curve && curve[get](parameter, true); + }; +}, { + preserve: true +}), +new function() { + + function insert(locations, loc, merge) { + var length = locations.length, + l = 0, + r = length - 1; + + function search(index, dir) { + for (var i = index + dir; i >= -1 && i <= length; i += dir) { + var loc2 = locations[((i % length) + length) % length]; + if (!loc.getPoint().isClose(loc2.getPoint(), + 2e-7)) + break; + if (loc.equals(loc2)) + return loc2; + } + return null; + } + + while (l <= r) { + var m = (l + r) >>> 1, + loc2 = locations[m], + found; + if (merge && (found = loc.equals(loc2) ? loc2 + : (search(m, -1) || search(m, 1)))) { + if (loc._overlap) { + found._overlap = found._intersection._overlap = true; + } + return found; + } + var path1 = loc.getPath(), + path2 = loc2.getPath(), + diff = path1 === path2 + ? (loc.getIndex() + loc.getParameter()) + - (loc2.getIndex() + loc2.getParameter()) + : path1._id - path2._id; + if (diff < 0) { + r = m - 1; + } else { + l = m + 1; + } + } + locations.splice(l, 0, loc); + return loc; + } + + return { statics: { + insert: insert, + + expand: function(locations) { + var expanded = locations.slice(); + for (var i = 0, l = locations.length; i < l; i++) { + insert(expanded, locations[i]._intersection, false); + } + return expanded; + } + }}; +}); var PathItem = Item.extend({ _class: 'PathItem', @@ -6611,60 +6911,64 @@ initialize: function PathItem() { }, - getIntersections: function(path, _matrix, _expand) { - if (this === path) - path = null; - var locations = [], - curves1 = this.getCurves(), - curves2 = path ? path.getCurves() : curves1, + getIntersections: function(path, include, _matrix, _returnFirst) { + var self = this === path || !path, matrix1 = this._matrix.orNullIfIdentity(), - matrix2 = path ? (_matrix || path._matrix).orNullIfIdentity() - : matrix1, + matrix2 = self ? matrix1 + : (_matrix || path._matrix).orNullIfIdentity(); + if (!self && !this.getBounds(matrix1).touches(path.getBounds(matrix2))) + return []; + var curves1 = this.getCurves(), + curves2 = self ? curves1 : path.getCurves(), length1 = curves1.length, - length2 = path ? curves2.length : length1, + length2 = self ? length1 : curves2.length, values2 = [], - tMin = 0.000001, - tMax = 1 - tMin; - if (path && !this.getBounds(matrix1).touches(path.getBounds(matrix2))) - return []; + arrays = [], + locations, + path; for (var i = 0; i < length2; i++) values2[i] = curves2[i].getValues(matrix2); for (var i = 0; i < length1; i++) { var curve1 = curves1[i], - values1 = path ? curve1.getValues(matrix1) : values2[i]; - if (!path) { - var seg1 = curve1.getSegment1(), - seg2 = curve1.getSegment2(), - h1 = seg1._handleOut, - h2 = seg2._handleIn; - if (new Line(seg1._point.subtract(h1), h1.multiply(2), true) - .intersect(new Line(seg2._point.subtract(h2), - h2.multiply(2), true), false)) { - var parts = Curve.subdivide(values1); - Curve.getIntersections( - parts[0], parts[1], curve1, curve1, locations, - function(loc) { - if (loc._parameter <= tMax) { - loc._parameter /= 2; - loc._parameter2 = 0.5 + loc._parameter2 / 2; - return true; - } - } - ); - } - } - for (var j = path ? 0 : i + 1; j < length2; j++) { - Curve.getIntersections( - values1, values2[j], curve1, curves2[j], locations, - !path && (j === i + 1 || j === length2 - 1 && i === 0) - && function(loc) { - var t = loc._parameter; - return t >= tMin && t <= tMax; - } + values1 = self ? values2[i] : curve1.getValues(matrix1), + path1 = curve1.getPath(); + if (path1 !== path) { + path = path1; + locations = []; + arrays.push(locations); + } + if (self) { + Curve._getSelfIntersection(values1, curve1, locations, { + include: include, + startConnected: length1 === 1 && + curve1.getPoint1().equals(curve1.getPoint2()) + }); + } + for (var j = self ? i + 1 : 0; j < length2; j++) { + if (_returnFirst && locations.length) + return locations; + var curve2 = curves2[j]; + Curve._getIntersections( + values1, values2[j], curve1, curve2, locations, + { + include: include, + startConnected: self && curve1.getPrevious() === curve2, + endConnected: self && curve1.getNext() === curve2 + } ); } } - return Curve.filterIntersections(locations, _expand); + locations = []; + for (var i = 0, l = arrays.length; i < l; i++) { + locations.push.apply(locations, arrays[i]); + } + return locations; + }, + + getCrossings: function(path) { + return this.getIntersections(path, function(inter) { + return inter.isCrossing(); + }); }, _asPathItem: function() { @@ -6840,14 +7144,14 @@ var parent = this._parent; if (parent) parent._currentPath = undefined; - this._length = this._clockwise = undefined; + this._length = this._area = this._clockwise = this._monoCurves = + undefined; if (flags & 16) { this._version++; } else if (this._curves) { for (var i = 0, l = this._curves.length; i < l; i++) this._curves[i]._changed(); } - this._monoCurves = undefined; } else if (flags & 32) { this._bounds = undefined; } @@ -6977,24 +7281,6 @@ return this._segments.length === 0; }, - isLinear: function() { - var segments = this._segments; - for (var i = 0, l = segments.length; i < l; i++) { - if (!segments[i].isLinear()) - return false; - } - return true; - }, - - hasHandles: function() { - var segments = this._segments; - for (var i = 0, l = segments.length; i < l; i++) { - if (segments[i].hasHandles()) - return true; - } - return false; - }, - _transformContent: function(matrix) { var coords = new Array(6); for (var i = 0, l = this._segments.length; i < l; i++) @@ -7024,12 +7310,11 @@ for (var i = index + amount, l = segments.length; i < l; i++) segments[i]._index = i; } - if (curves || segs._curves) { - if (!curves) - curves = this._curves = []; - var from = index > 0 ? index - 1 : index, + if (curves) { + var total = this._countCurves(), + from = index + amount - 1 === total ? index - 1 : index, start = from, - to = Math.min(from + amount, this._countCurves()); + to = Math.min(from + amount, total); if (segs._curves) { curves.splice.apply(curves, [from, 0].concat(segs._curves)); start += segs._curves.length; @@ -7134,22 +7419,57 @@ clear: '#removeSegments', + hasHandles: function() { + var segments = this._segments; + for (var i = 0, l = segments.length; i < l; i++) { + if (segments[i].hasHandles()) + return true; + } + return false; + }, + + clearHandles: function() { + var segments = this._segments; + for (var i = 0, l = segments.length; i < l; i++) + segments[i].clearHandles(); + }, + getLength: function() { if (this._length == null) { - var curves = this.getCurves(); - this._length = 0; + var curves = this.getCurves(), + length = 0; for (var i = 0, l = curves.length; i < l; i++) - this._length += curves[i].getLength(); + length += curves[i].getLength(); + this._length = length; } return this._length; }, getArea: function() { - var curves = this.getCurves(); - var area = 0; - for (var i = 0, l = curves.length; i < l; i++) - area += curves[i].getArea(); - return area; + if (this._area == null) { + var segments = this._segments, + count = segments.length, + last = count - 1, + area = 0; + for (var i = 0, l = this._closed ? count : last; i < l; i++) { + area += Curve.getArea(Curve.getValues( + segments[i], segments[i < last ? i + 1 : 0])); + } + this._area = area; + } + return this._area; + }, + + isClockwise: function() { + if (this._clockwise !== undefined) + return this._clockwise; + return this.getArea() >= 0; + }, + + setClockwise: function(clockwise) { + if (this.isClockwise() != (clockwise = !!clockwise)) + this.reverse(); + this._clockwise = clockwise; }, isFullySelected: function() { @@ -7203,7 +7523,8 @@ var curves = this.getCurves(); for (var i = curves.length - 1; i >= 0; i--) { var curve = curves[i]; - if (curve.isLinear() && curve.getLength() === 0) + if (!curve.hasHandles() && (curve.getLength() === 0 + || curve.isCollinear(curve.getNext()))) curve.remove(); } return this; @@ -7228,14 +7549,15 @@ index = arg.index; parameter = arg.parameter; } - var tolerance = 0.000001; - if (parameter >= 1 - tolerance) { + var tMin = 4e-7, + tMax = 1 - tMin; + if (parameter >= tMax) { index++; parameter--; } var curves = this.getCurves(); if (index >= 0 && index < curves.length) { - if (parameter > tolerance) { + if (parameter >= tMin) { curves[index++].divide(parameter, true); } var segs = this.removeSegments(index, this._segments.length, true), @@ -7244,7 +7566,9 @@ this.setClosed(false); path = this; } else { - path = this._clone(new Path().insertAbove(this, true)); + path = new Path(Item.NO_INSERT); + path.insertAbove(this, true); + this._clone(path); } path._add(segs, 0); this.addSegment(segs[0]); @@ -7253,18 +7577,6 @@ return null; }, - isClockwise: function() { - if (this._clockwise !== undefined) - return this._clockwise; - return Path.isClockwise(this._segments); - }, - - setClockwise: function(clockwise) { - if (this.isClockwise() != (clockwise = !!clockwise)) - this.reverse(); - this._clockwise = clockwise; - }, - reverse: function() { this._segments.reverse(); for (var i = 0, l = this._segments.length; i < l; i++) { @@ -7305,7 +7617,7 @@ this._add(segments.slice()); } } - if (path.closed) + if (path._closed) this._add([segments[0]]); path.remove(); } @@ -7330,15 +7642,43 @@ 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 = 0.5522847498307936; + if (handle1.isOrthogonal(handle2)) { + var pt1 = seg1._point, + pt2 = seg2._point, + 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) { @@ -7515,22 +7855,6 @@ { beans: false, - _getOffset: function(location) { - var index = location && location.getIndex(); - if (index != null) { - var curves = this.getCurves(), - offset = 0; - for (var i = 0; i < index; i++) - offset += curves[i].getLength(); - var curve = curves[index], - parameter = location.getParameter(); - if (parameter > 0) - offset += curve.getPartLength(0, parameter); - return offset; - } - return null; - }, - getLocationOf: function() { var point = Point.read(arguments), curves = this.getCurves(); @@ -7563,7 +7887,7 @@ return curve.getLocationAt(offset - start); } } - if (offset <= this.getLength()) + if (curves.length > 0 && offset <= this.getLength()) return new CurveLocation(curves[curves.length - 1], 1); return null; }, @@ -7586,7 +7910,8 @@ getNearestPoint: function() { return this.getNearestLocation.apply(this, arguments).getPoint(); } -}), new function() { +}), +new function() { function drawHandles(ctx, segments, matrix, size) { var half = size / 2; @@ -7750,8 +8075,8 @@ drawHandles(ctx, this._segments, matrix, paper.settings.handleSize); } }; -}, new function() { - +}, +new function() { function getFirstControlPoints(rhs) { var n = rhs.length, x = [], @@ -7843,7 +8168,8 @@ } } }; -}, new function() { +}, +new function() { function getCurrentSegment(that) { var segments = that._segments; if (segments.length === 0) @@ -7929,7 +8255,6 @@ x = pt.x, y = pt.y, abs = Math.abs, - epsilon = 1e-12, rx = abs(radius.width), ry = abs(radius.height), rxSq = rx * rx, @@ -7945,7 +8270,7 @@ } factor = (rxSq * rySq - rxSq * ySq - rySq * xSq) / (rxSq * ySq + rySq * xSq); - if (abs(factor) < epsilon) + if (abs(factor) < 1e-12) factor = 0; if (factor < 0) throw new Error( @@ -8075,14 +8400,6 @@ }, statics: { - isClockwise: function(segments) { - var sum = 0; - for (var i = 0, l = segments.length; i < l; i++) - sum += Curve.getEdgeSum(Curve.getValues( - segments[i], segments[i + 1 < l ? i + 1 : 0])); - return sum > 0; - }, - getBounds: function(segments, closed, style, matrix, strokePadding) { var first = segments[0]; if (!first) @@ -8427,6 +8744,13 @@ }, insertChildren: function insertChildren(index, items, _preserve) { + for (var i = items.length - 1; i >= 0; i--) { + var item = items[i]; + if (item instanceof CompoundPath) { + items.splice.apply(items, [i, 1].concat(item.removeChildren())); + item.remove(); + } + } items = insertChildren.base.call(this, index, items, _preserve, Path); for (var i = 0, l = !_preserve && items && items.length; i < l; i++) { var item = items[i]; @@ -8448,15 +8772,20 @@ }, reduce: function reduce() { - if (this._children.length === 0) { + var children = this._children; + for (var i = children.length - 1; i >= 0; i--) { + var path = children[i].reduce(); + if (path.isEmpty()) + children.splice(i, 1); + } + if (children.length === 0) { var path = new Path(Item.NO_INSERT); path.insertAbove(this); path.setStyle(this._style); this.remove(); return path; - } else { - return reduce.base.call(this); - } + } + return reduce.base.call(this); }, isClockwise: function() { @@ -8514,7 +8843,7 @@ var child = children[i], mx = child._matrix; paths.push(child.getPathData(_matrix && !mx.isIdentity() - ? _matrix.chain(mx) : mx, _precision)); + ? _matrix.chain(mx) : _matrix, _precision)); } return paths.join(' '); } @@ -8562,7 +8891,8 @@ : matrix.chain(mx)); } } -}, new function() { +}, +new function() { function getCurrentPath(that, check) { var children = that._children; if (check && children.length === 0) @@ -8573,7 +8903,8 @@ var fields = { moveTo: function() { var current = getCurrentPath(this), - path = current && current.isEmpty() ? current : new Path(); + path = current && current.isEmpty() ? current + : new Path(Item.NO_INSERT); if (path !== current) this.addChild(path); path.moveTo.apply(path, arguments); @@ -8623,24 +8954,40 @@ } }; + function preparePath(path, resolve) { + var res = path.clone(false).reduce().transform(null, true, true); + return resolve ? res.resolveCrossings().reorient() : res; + } + + function finishBoolean(ctor, paths, path1, path2, reduce) { + var result = new ctor(Item.NO_INSERT); + result.addChildren(paths, true); + if (reduce) + result = result.reduce(); + result.insertAbove(path2 && path1.isSibling(path2) + && path1.getIndex() < path2.getIndex() + ? path2 : path1); + result.setStyle(path1._style); + return result; + } + function computeBoolean(path1, path2, operation) { - var operator = operators[operation]; - function preparePath(path) { - return path.clone(false).reduce().reorient().transform(null, true, - true); - } - - var _path1 = preparePath(path1), - _path2 = path2 && path1 !== path2 && preparePath(path2); + if (!path1._children && !path1._closed) + return computeOpenBoolean(path1, path2, operation); + var _path1 = preparePath(path1, true), + _path2 = path2 && path1 !== path2 && preparePath(path2, true); if (_path2 && /^(subtract|exclude)$/.test(operation) ^ (_path2.isClockwise() !== _path1.isClockwise())) _path2.reverse(); - splitPath(_path1.getIntersections(_path2, null, true)); - - var chain = [], - segments = [], - monoCurves = [], - tolerance = 0.000001; + var intersections = CurveLocation.expand( + _path1.getIntersections(_path2, function(inter) { + return _path2 && inter.isOverlap() || inter.isCrossing(); + }) + ); + divideLocations(intersections); + + var segments = [], + monoCurves = []; function collect(paths) { for (var i = 0, l = paths.length; i < l; i++) { @@ -8653,114 +9000,121 @@ collect(_path1._children || [_path1]); if (_path2) collect(_path2._children || [_path2]); - segments.sort(function(a, b) { - var _a = a._intersection, - _b = b._intersection; - return !_a && !_b || _a && _b ? 0 : _a ? -1 : 1; - }); + for (var i = 0, l = intersections.length; i < l; i++) { + propagateWinding(intersections[i]._segment, _path1, _path2, + monoCurves, operation); + } for (var i = 0, l = segments.length; i < l; i++) { var segment = segments[i]; - if (segment._winding != null) - continue; - chain.length = 0; - var startSeg = segment, - totalLength = 0, - windingSum = 0; - do { - var length = segment.getCurve().getLength(); - chain.push({ segment: segment, length: length }); - totalLength += length; - segment = segment.getNext(); - } while (segment && !segment._intersection && segment !== startSeg); - for (var j = 0; j < 3; j++) { - var length = totalLength * (j + 1) / 4; - for (var k = 0, m = chain.length; k < m; k++) { - var node = chain[k], - curveLength = node.length; - if (length <= curveLength) { - if (length < tolerance - || curveLength - length < tolerance) - length = curveLength / 2; - var curve = node.segment.getCurve(), - pt = curve.getPointAt(length), - hor = curve.isLinear() && Math.abs(curve - .getTangentAt(0.5, true).y) < tolerance, - path = curve._path; - if (path._parent instanceof CompoundPath) - path = path._parent; - windingSum += operation === 'subtract' && _path2 - && (path === _path1 && _path2._getWinding(pt, hor) - || path === _path2 && !_path1._getWinding(pt, hor)) - ? 0 - : getWinding(pt, monoCurves, hor); - break; - } - length -= curveLength; - } - } - var winding = Math.round(windingSum / 3); - for (var j = chain.length - 1; j >= 0; j--) - chain[j].segment._winding = winding; - } - var result = new CompoundPath(Item.NO_INSERT); - result.insertAbove(path1); - result.addChildren(tracePaths(segments, operator), true); - result = result.reduce(); - result.setStyle(path1._style); - return result; - } - - function splitPath(intersections) { - var tMin = 0.000001, + if (segment._winding == null) { + propagateWinding(segment, _path1, _path2, monoCurves, + operation); + } + } + return finishBoolean(CompoundPath, tracePaths(segments, operation), + path1, path2, true); + } + + function computeOpenBoolean(path1, path2, operation) { + if (!path2 || !path2._children && !path2._closed + || !/^(subtract|intersect)$/.test(operation)) + return null; + var _path1 = preparePath(path1, false), + _path2 = preparePath(path2, false), + intersections = _path1.getIntersections(_path2, function(inter) { + return inter.isOverlap() || inter.isCrossing(); + }), + sub = operation === 'subtract', + paths = []; + + function addPath(path) { + if (_path2.contains(path.getPointAt(path.getLength() / 2)) ^ sub) { + paths.unshift(path); + return true; + } + } + + for (var i = intersections.length - 1; i >= 0; i--) { + var path = intersections[i].split(); + if (path) { + if (addPath(path)) + path.getFirstSegment().setHandleIn(0, 0); + _path1.getLastSegment().setHandleOut(0, 0); + } + } + addPath(_path1); + return finishBoolean(Group, paths, path1, path2); + } + + function linkIntersections(from, to) { + var prev = from; + while (prev) { + if (prev === to) + return; + prev = prev._prev; + } + while (from._next && from._next !== to) + from = from._next; + if (!from._next) { + while (to._prev) + to = to._prev; + from._next = to; + to._prev = from; + } + } + + function divideLocations(locations) { + var tMin = 4e-7, tMax = 1 - tMin, - linearHandles; - - function resetLinear() { - for (var i = 0, l = linearHandles.length; i < l; i++) - linearHandles[i].set(0, 0); - } - - for (var i = intersections.length - 1, curve, prev; i >= 0; i--) { - var loc = intersections[i], - t = loc._parameter; - if (prev && prev._curve === loc._curve && prev._parameter > 0) { - t /= prev._parameter; + noHandles = false, + clearSegments = [], + prevCurve, + prevT; + + for (var i = locations.length - 1; i >= 0; i--) { + var loc = locations[i], + curve = loc._curve, + t = loc._parameter, + origT = t; + if (curve !== prevCurve) { + noHandles = !curve.hasHandles(); + } else if (prevT > 0) { + t /= prevT; + } + var segment; + if (t < tMin) { + segment = curve._segment1; + } else if (t > tMax) { + segment = curve._segment2; } else { - curve = loc._curve; - if (linearHandles) - resetLinear(); - linearHandles = curve.isLinear() ? [ - curve._segment1._handleOut, - curve._segment2._handleIn - ] : null; - } - var newCurve, - segment; - if (newCurve = curve.divide(t, true, true)) { - segment = newCurve._segment1; - curve = newCurve.getPrevious(); - if (linearHandles) - linearHandles.push(segment._handleOut, segment._handleIn); + segment = curve.divide(t, true, true)._segment1; + if (noHandles) + clearSegments.push(segment); + } + loc._setSegment(segment); + var inter = segment._intersection, + dest = loc._intersection; + if (inter) { + linkIntersections(inter, dest); + var other = inter; + while (other) { + linkIntersections(other._intersection, inter); + other = other._next; + } } else { - segment = t < tMin - ? curve._segment1 - : t > tMax - ? curve._segment2 - : curve.getPartLength(0, t) < curve.getPartLength(t, 1) - ? curve._segment1 - : curve._segment2; - } - segment._intersection = loc.getIntersection(); - loc._segment = segment; - prev = loc; - } - if (linearHandles) - resetLinear(); + segment._intersection = dest; + } + prevCurve = curve; + prevT = origT; + } + for (var i = 0, l = clearSegments.length; i < l; i++) { + clearSegments[i].clearHandles(); + } } function getWinding(point, curves, horizontal, testContains) { - var tolerance = 0.000001, - tMin = tolerance, + var epsilon = 2e-7, + tMin = 4e-7, tMax = 1 - tMin, px = point.x, py = point.y, @@ -8771,8 +9125,8 @@ if (horizontal) { var yTop = -Infinity, yBottom = Infinity, - yBefore = py - tolerance, - yAfter = py + tolerance; + yBefore = py - epsilon, + yAfter = py + epsilon; for (var i = 0, l = curves.length; i < l; i++) { var values = curves[i].values; if (Curve.solveCubic(values, 0, px, roots, 0, 1) > 0) { @@ -8789,12 +9143,14 @@ yTop = (yTop + py) / 2; yBottom = (yBottom + py) / 2; if (yTop > -Infinity) - windLeft = getWinding(new Point(px, yTop), curves); + windLeft = getWinding(new Point(px, yTop), curves, false, + testContains); if (yBottom < Infinity) - windRight = getWinding(new Point(px, yBottom), curves); + windRight = getWinding(new Point(px, yBottom), curves, false, + testContains); } else { - var xBefore = px - tolerance, - xAfter = px + tolerance; + var xBefore = px - epsilon, + xAfter = px + epsilon; var startCounted = false, prevCurve, prevT; @@ -8814,7 +9170,7 @@ var x = Curve.getPoint(values, t).x, slope = Curve.getTangent(values, t).y, counted = false; - if (Numerical.isZero(slope) && !Curve.isLinear(values) + if (Numerical.isZero(slope) && !Curve.isStraight(values) || t < tMin && slope * Curve.getTangent( curve.previous.values, 1).y < 0 || t > tMax && slope * Curve.getTangent( @@ -8842,81 +9198,157 @@ return Math.max(abs(windLeft), abs(windRight)); } - function tracePaths(segments, operator, selfOp) { + function propagateWinding(segment, path1, path2, monoCurves, operation) { + var epsilon = 2e-7, + chain = [], + start = segment, + totalLength = 0, + windingSum = 0; + do { + var curve = segment.getCurve(), + length = curve.getLength(); + chain.push({ segment: segment, curve: curve, length: length }); + totalLength += length; + segment = segment.getNext(); + } while (segment && !segment._intersection && segment !== start); + for (var i = 0; i < 3; i++) { + var length = totalLength * (i + 1) / 4; + for (var k = 0, m = chain.length; k < m; k++) { + var node = chain[k], + curveLength = node.length; + if (length <= curveLength) { + if (length < epsilon || curveLength - length < epsilon) + length = curveLength / 2; + var curve = node.curve, + path = curve._path, + parent = path._parent, + pt = curve.getPointAt(length), + hor = curve.isHorizontal(); + if (parent instanceof CompoundPath) + path = parent; + windingSum += operation === 'subtract' && path2 + && (path === path1 && path2._getWinding(pt, hor) + || path === path2 && !path1._getWinding(pt, hor)) + ? 0 + : getWinding(pt, monoCurves, hor); + break; + } + length -= curveLength; + } + } + var winding = Math.round(windingSum / 3); + for (var j = chain.length - 1; j >= 0; j--) + chain[j].segment._winding = winding; + } + + function tracePaths(segments, operation) { var paths = [], - tMin = 0.000001, - tMax = 1 - tMin; - for (var i = 0, seg, startSeg, l = segments.length; i < l; i++) { - seg = startSeg = segments[i]; - if (seg._visited || !operator(seg._winding)) + start, + otherStart, + operator = operators[operation], + overlapWinding = { + unite: { 1: 2 }, + intersect: { 2: 1 } + }[operation]; + + function isValid(seg, adjusted) { + if (seg._visited) + return false; + if (!operator) + return true; + var winding = seg._winding, + inter = seg._intersection; + if (inter && adjusted && overlapWinding && inter.isOverlap()) + winding = overlapWinding[winding] || winding; + return operator(winding); + } + + function isStart(seg) { + return seg === start || seg === otherStart; + } + + function findBestIntersection(inter, strict) { + if (!inter._next) + return inter; + while (inter) { + var seg = inter._segment, + nextSeg = seg.getNext(), + nextInter = nextSeg._intersection; + if (isStart(nextSeg) + || !seg._visited && !nextSeg._visited + && (!operator + || (!strict || isValid(seg)) + && (!(strict && nextInter && nextInter.isOverlap()) + && isValid(nextSeg) + || !strict && nextInter + && isValid(nextInter._segment)) + )) + return inter; + inter = inter._next; + } + return null; + } + + function findStartSegment(inter, next) { + while (inter) { + var seg = inter._segment; + if (isStart(seg)) + return seg; + inter = inter[next ? '_next' : '_prev']; + } + } + + for (var i = 0, l = segments.length; i < l; i++) { + var seg = segments[i], + path = null, + finished = false; + if (!isValid(seg, true)) continue; - var path = new Path(Item.NO_INSERT), - inter = seg._intersection, - startInterSeg = inter && inter._segment, - added = false, - dir = 1; - do { - var handleIn = dir > 0 ? seg._handleIn : seg._handleOut, - handleOut = dir > 0 ? seg._handleOut : seg._handleIn, - interSeg; - if (added && (!operator(seg._winding) || selfOp) - && (inter = seg._intersection) - && (interSeg = inter._segment) - && interSeg !== startSeg) { - if (selfOp) { - seg._visited = interSeg._visited; - seg = interSeg; - dir = 1; - } else { - var c1 = seg.getCurve(); - if (dir > 0) - c1 = c1.getPrevious(); - var t1 = c1.getTangentAt(dir < 1 ? tMin : tMax, true), - c4 = interSeg.getCurve(), - c3 = c4.getPrevious(), - t3 = c3.getTangentAt(tMax, true), - t4 = c4.getTangentAt(tMin, true), - w3 = t1.cross(t3), - w4 = t1.cross(t4); - if (w3 * w4 !== 0) { - var curve = w3 < w4 ? c3 : c4, - nextCurve = operator(curve._segment1._winding) - ? curve - : w3 < w4 ? c4 : c3, - nextSeg = nextCurve._segment1; - dir = nextCurve === c3 ? -1 : 1; - if (nextSeg._visited && seg._path !== nextSeg._path - || !operator(nextSeg._winding)) { - dir = 1; - } else { - seg._visited = interSeg._visited; - seg = interSeg; - if (nextSeg._visited) - dir = 1; - } - } else { - dir = 1; + start = otherStart = null; + while (!finished) { + var inter = seg._intersection, + handleIn = path && seg._handleIn; + inter = inter && (findBestIntersection(inter, true) + || findBestIntersection(inter, false)) || inter; + var other = inter && inter._segment; + if (other && isValid(other)) + seg = other; + if (seg._visited) { + finished = isStart(seg); + if (!finished && inter) { + var found = findStartSegment(inter, true) + || findStartSegment(inter, false); + if (found) { + seg = found; + finished = true; } } - handleOut = dir > 0 ? seg._handleOut : seg._handleIn; - } - path.add(new Segment(seg._point, added && handleIn, handleOut)); - added = true; + break; + } + if (!path) { + path = new Path(Item.NO_INSERT); + start = seg; + otherStart = other; + } + path.add(new Segment(seg._point, handleIn, seg._handleOut)); seg._visited = true; - seg = dir > 0 ? seg.getNext() : seg. getPrevious(); - } while (seg && !seg._visited - && seg !== startSeg && seg !== startInterSeg - && (seg._intersection || operator(seg._winding))); - if (seg && (seg === startSeg || seg === startInterSeg)) { - path.firstSegment.setHandleIn((seg === startInterSeg - ? startInterSeg : seg)._handleIn); + seg = seg.getNext(); + finished = isStart(seg); + } + if (finished) { + path.firstSegment.setHandleIn(seg._handleIn); path.setClosed(true); - } else { - path.lastSegment._handleOut.set(0, 0); - } - if (path._segments.length > - (path._closed ? path.isLinear() ? 2 : 0 : 1)) + } else if (path) { + console.error('Boolean operation resulted in open path', + 'segments =', path._segments.length, + 'length =', path.getLength()); + path = null; + } + if (path && (path._segments.length > 8 + || !Numerical.isZero(path.getArea()))) { paths.push(path); + path = null; + } } return paths; } @@ -8944,7 +9376,23 @@ }, divide: function(path) { - return new Group([this.subtract(path), this.intersect(path)]); + return finishBoolean(Group, + [this.subtract(path), this.intersect(path)], + this, path, true); + }, + + resolveCrossings: function() { + var crossings = this.getCrossings(); + if (!crossings.length) + return this; + divideLocations(CurveLocation.expand(crossings)); + var paths = this._children || [this], + segments = []; + for (var i = 0, l = paths.length; i < l; i++) { + segments.push.apply(segments, paths[i]._segments); + } + return finishBoolean(CompoundPath, tracePaths(segments), + this, null, false); } }; }); @@ -8980,24 +9428,24 @@ y1 = v[3], y2 = v[5], y3 = v[7]; - if (Curve.isLinear(v)) { + if (Curve.isStraight(v)) { insertCurve(v); } else { var a = 3 * (y1 - y2) - y0 + y3, b = 2 * (y0 + y2) - 4 * y1, c = y1 - y0, - tolerance = 0.000001, - roots = []; - var count = Numerical.solveQuadratic(a, b, c, roots, tolerance, - 1 - tolerance); - if (count === 0) { + tMin = 4e-7, + tMax = 1 - tMin, + roots = [], + n = Numerical.solveQuadratic(a, b, c, roots, tMin, tMax); + if (n === 0) { insertCurve(v); } else { roots.sort(); var t = roots[0], parts = Curve.subdivide(v, t); insertCurve(parts[0]); - if (count > 1) { + if (n > 1) { t = (roots[1] - t) / (1 - t); parts = Curve.subdivide(parts[1], t); insertCurve(parts[0]); @@ -9112,7 +9560,7 @@ function computeParts(curve, index, minT, maxT) { if ((maxT - minT) > minDifference && !Curve.isFlatEnough(curve, tolerance || 0.25)) { - var split = Curve.subdivide(curve), + var split = Curve.subdivide(curve, 0.5), halfT = (minT + maxT) / 2; computeParts(split[0], index, minT, halfT); computeParts(split[1], index, halfT, maxT); @@ -9120,7 +9568,7 @@ var x = curve[6] - curve[0], y = curve[7] - curve[1], dist = Math.sqrt(x * x + y * y); - if (dist > 0.000001) { + if (dist > 1e-6) { length += dist; parts.push({ offset: length, @@ -9366,7 +9814,7 @@ pt2 = this.evaluate(1, curve2, u), diff = pt.subtract(point), df = pt1.dot(pt1) + diff.dot(pt2); - if (Math.abs(df) < 0.000001) + if (Math.abs(df) < 1e-6) return u; return u - diff.dot(pt1) / df; }, @@ -9801,7 +10249,7 @@ : 'gray' in arg ? 'gray' : 'rgb'; - var properties = types[type]; + var properties = types[type], parsers = componentParsers[type]; this._components = components = []; for (var i = 0, l = properties.length; i < l; i++) { @@ -10003,7 +10451,8 @@ } } }); -}, new function() { +}, +new function() { var operators = { add: function(a, b) { return a + b; @@ -10039,24 +10488,6 @@ }); }); -Base.each(Color._types, function(properties, type) { - var ctor = this[Base.capitalize(type) + 'Color'] = function(arg) { - var argType = arg != null && typeof arg, - components = argType === 'object' && arg.length != null - ? arg - : argType === 'string' - ? null - : arguments; - return components - ? new Color(type, components) - : new Color(arg); - }; - if (type.length == 3) { - var acronym = type.toUpperCase(); - Color[acronym] = this[acronym + 'Color'] = ctor; - } -}, Base.exports); - var Gradient = Base.extend({ _class: 'Gradient', @@ -10681,19 +11112,29 @@ return true; }, - _events: { - onFrame: { - install: function() { - this.play(); - }, - - uninstall: function() { - this.pause(); - } - }, - - onResize: {} - }, + _events: Base.each(['onResize', 'onMouseDown', 'onMouseUp', 'onMouseMove'], + function(name) { + this[name] = { + install: function(type) { + this._installEvent(type); + }, + + uninstall: function(type) { + this._uninstallEvent(type); + } + }; + }, { + onFrame: { + install: function() { + this.play(); + }, + + uninstall: function() { + this.pause(); + } + } + } + ), _animate: false, _time: 0, @@ -10888,7 +11329,8 @@ return new CanvasView(project, element); } } -}, new function() { +}, +new function() { var tool, prevFocus, tempFocus, @@ -11011,11 +11453,50 @@ load: updateFocus }); + var mouseFlags = { + mousedown: { + mousedown: 1, + mousedrag: 1, + click: 1, + doubleclick: 1 + }, + mouseup: { + mouseup: 1, + mousedrag: 1, + click: 1, + doubleclick: 1 + }, + mousemove: { + mousedrag: 1, + mousemove: 1, + mouseenter: 1, + mouseleave: 1 + } + }; + return { _viewEvents: viewEvents, _handleEvent: function() {}, + _installEvent: function(type) { + var counters = this._eventCounters; + if (counters) { + for (var key in mouseFlags) { + counters[key] = (counters[key] || 0) + + (mouseFlags[key][type] || 0); + } + } + }, + + _uninstallEvent: function(type) { + var counters = this._eventCounters; + if (counters) { + for (var key in mouseFlags) + counters[key] -= mouseFlags[key][type] || 0; + } + }, + statics: { updateFocus: updateFocus } @@ -11105,8 +11586,8 @@ project._needsUpdate = false; return true; } -}, new function() { - +}, +new function() { var downPoint, lastPoint, overPoint, @@ -11551,8 +12032,7 @@ }, setFixedDistance: function(distance) { - this._minDistance = distance; - this._maxDistance = distance; + this._minDistance = this._maxDistance = distance; }, _updateEvent: function(type, point, minDistance, maxDistance, start,