From 04452730dd37cce383450dc602b8c2c35e134045 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?J=C3=BCrg=20Lehni?= Date: Wed, 9 Sep 2015 17:17:49 +0200 Subject: [PATCH] Simplify CurveLocation data structures. Directly creating and linking intersections simplifies things a lot. --- examples/Scripts/BooleanOperations.html | 2 +- src/path/Curve.js | 72 ++++++++++--------------- src/path/CurveLocation.js | 72 +++++++++++++------------ src/path/PathItem.Boolean.js | 10 ++-- src/path/PathItem.js | 8 +-- 5 files changed, 74 insertions(+), 90 deletions(-) diff --git a/examples/Scripts/BooleanOperations.html b/examples/Scripts/BooleanOperations.html index e12356ff..7520ab6c 100644 --- a/examples/Scripts/BooleanOperations.html +++ b/examples/Scripts/BooleanOperations.html @@ -269,7 +269,7 @@ // // annotatePath(pathB) // // pathB.translate([ 300, 0 ]); // // pathB.segments.filter(function(a) { return a._ixPair; }).map( - // // function(a) { a._ixPair.getIntersection()._segment.selected = true; }); + // // function(a) { a._ixPair.intersection._segment.selected = true; }); // console.time('unite'); // var nup = unite(pathA, pathB); diff --git a/src/path/Curve.js b/src/path/Curve.js index 32b6eb25..e0b47412 100644 --- a/src/path/Curve.js +++ b/src/path/Curve.js @@ -1000,8 +1000,7 @@ statics: { step /= 2; } var pt = Curve.getPoint(values, minT); - return new CurveLocation(this, minT, pt, null, null, null, - point.getDistance(pt)); + return new CurveLocation(this, minT, pt, point.getDistance(pt)); }, /** @@ -1317,7 +1316,8 @@ new function() { // Scope for methods that require private functions }, new function() { // Scope for intersection using bezier fat-line clipping - function addLocation(locations, param, v1, c1, t1, p1, v2, c2, t2, p2) { + function addLocation(locations, param, v1, c1, t1, p1, v2, c2, t2, p2, + overlap) { var loc = null, tMin = /*#=*/Numerical.TOLERANCE, tMax = 1 - tMin; @@ -1327,11 +1327,16 @@ new function() { // Scope for intersection using bezier fat-line clipping && t1 <= (param.endConnected ? tMax : 1)) { if (t2 == null) t2 = Curve.getParameterOf(v2, p2.x, p2.y); - loc = new CurveLocation( - c1, t1, p1 || Curve.getPoint(v1, t1), - c2, t2, p2 || Curve.getPoint(v2, t2)); - if (param.adjust) - param.adjust(loc); + var reparametrize = param.reparametrize; + if (reparametrize) { + var res = reparametrize(t1, t2); + t1 = res.t1; + t2 = res.t2; + } + loc = new CurveLocation(c1, t1, p1 || Curve.getPoint(v1, t1), + null, overlap, + new CurveLocation(c2, t2, p2 || Curve.getPoint(v2, t2), + null, overlap)); locations.push(loc); } return loc; @@ -1662,18 +1667,10 @@ new function() { // Scope for intersection using bezier fat-line clipping abs(p2[4] - p1[4]) < epsilon && abs(p2[5] - p1[5]) < epsilon) { // Overlapping parts are identical - var t11 = pairs[0][0], - t12 = pairs[0][1], - t21 = pairs[1][0], - t22 = pairs[1][1], - loc1 = addLocation(locations, param, v1, c1, t11, null, - v2, c2, t12, null), - loc2 = addLocation(locations, param, v1, c1, t21, null, - v2, c2, t22, null); - if (loc1) - loc1._overlap = true; - if (loc2) - loc2._overlap = true; + addLocation(locations, param, v1, c1, pairs[0][0], null, + v2, c2, pairs[0][1], null, true), + addLocation(locations, param, v1, c1, pairs[1][0], null, + v2, c2, pairs[1][1], null, true); return true; } } @@ -1736,37 +1733,22 @@ new function() { // Scope for intersection using bezier fat-line clipping }, _filterIntersections: function(locations, expand) { - var last = locations.length - 1, - tMax = 1 - /*#=*/Numerical.TOLERANCE; - // Merge intersections very close to the end of a curve to the - // beginning of the next curve, so we can compare them. - for (var i = last; i >= 0; i--) { - var loc = locations[i], - next; - if (loc._parameter >= tMax && (next = loc._curve.getNext())) { - loc._parameter = 0; - loc._curve = next; - } - if (loc._parameter2 >= tMax && (next = loc._curve2.getNext())) { - loc._parameter2 = 0; - loc._curve2 = next; - } - } - + var last = locations.length - 1; if (last > 0) { CurveLocation.sort(locations); - // Filter out duplicate locations, but preserve _overlap setting - // among all duplicated (only one of them will have it defined). + // Filter out duplicate locations, but preserve _overlap among + // all duplicated (only one of them will have it defined). var i = last, loc = locations[i]; while(--i >= 0) { var prev = locations[i]; if (prev.equals(loc)) { - locations.splice(i + 1, 1); // Remove loc. - // Preserve overlap setting. - var overlap = loc._overlap; - if (overlap) - prev._overlap = overlap; + locations.splice(i + 1, 1); // Remove location. + // Preserve _overlap for both linked intersections. + var over = loc._overlap; + if (over) { + prev._overlap = prev._intersection._overlap = over; + } last--; } loc = prev; @@ -1774,7 +1756,7 @@ new function() { // Scope for intersection using bezier fat-line clipping } if (expand) { for (var i = last; i >= 0; i--) - locations.push(locations[i].getIntersection()); + locations.push(locations[i]._intersection); CurveLocation.sort(locations); } return locations; diff --git a/src/path/CurveLocation.js b/src/path/CurveLocation.js index 2bec1df0..fd3e056c 100644 --- a/src/path/CurveLocation.js +++ b/src/path/CurveLocation.js @@ -41,8 +41,17 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ * @param {Number} parameter * @param {Point} [point] */ - initialize: function CurveLocation(curve, parameter, point, _curve2, - _parameter2, _point2, _distance) { + initialize: function CurveLocation(curve, parameter, point, + _distance, _overlap, _intersection) { + // Merge intersections very close to the end of a curve to the + // beginning of the next curve. + if (parameter >= 1 - /*#=*/Numerical.TOLERANCE) { + var next = curve.getNext(); + if (next) { + parameter = 0; + curve = next; + } + } // Define this CurveLocation's unique id. // NOTE: We do not use the same pool as the rest of the library here, // since this is only required to be unique at runtime among other @@ -53,10 +62,14 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ 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._overlap = _overlap; + this._intersection = _intersection; + this._other = false; + if (_intersection) { + _intersection._intersection = this; + _intersection._other = true; + } // Also store references to segment1 and segment2, in case path // splitting / dividing is going to happen, in which case the segments // can be used to determine the new curves, see #getCurve(true) @@ -209,17 +222,7 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ * @bean */ getIntersection: function() { - var intersection = this._intersection; - if (!intersection && this._curve2) { - // If we have the parameter on the other curve use that for - // intersection rather than the point. - this._intersection = intersection = new CurveLocation(this._curve2, - this._parameter2, this._point2 || this._point); - intersection._overlap = this._overlap; - intersection._intersection = this; - intersection._other = true; - } - return intersection; + return this._intersection; }, /** @@ -275,23 +278,20 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ * @param {CurveLocation} location * @return {Boolean} {@true if the locations are equal} */ - equals: function(loc) { - var abs = Math.abs, - // Use the same tolerance for curve time parameter comparisons as - // in Curve.js when considering two locations the same. - tolerance = /*#=*/Numerical.TOLERANCE; + equals: function(loc, _ignoreIntersection) { return this === loc - || loc instanceof CurveLocation - // Call getCurve() and getParameter() to keep in sync - && this.getCurve() === loc.getCurve() - && abs(this.getParameter() - loc.getParameter()) < tolerance - // _curve2/_parameter2 are only used for Boolean operations - // and don't need syncing there. - // TODO: That's not quite true though... Rework this! - && this._curve2 === loc._curve2 - && abs((this._parameter2 || 0) - (loc._parameter2 || 0)) - < tolerance - || false; + || loc instanceof CurveLocation + // Call getCurve() and getParameter() to keep in sync + && this.getCurve() === loc.getCurve() + // Use the same tolerance for curve time parameter + // comparisons as in Curve.js + && Math.abs(this.getParameter() - loc.getParameter()) + < /*#=*/Numerical.TOLERANCE + && (_ignoreIntersection + || (!this._intersection && !loc._intersection + || this._intersection && this._intersection.equals( + loc._intersection, true))) + || false; }, /** @@ -329,10 +329,12 @@ var CurveLocation = Base.extend(/** @lends CurveLocation# */{ if (curve1 === curve2) { var diff = l1._parameter - l2._parameter; if (Math.abs(diff) < tolerance) { - var curve21 = l1._curve2, - curve22 = l2._curve2; + var i1 = l1._intersection, + i2 = l2._intersection, + curve21 = i1 && i1._curve, + curve22 = i2 && l2._curve; res = curve21 === curve22 // equal or both null - ? l1._parameter2 - l2._parameter2 + ? (i1 ? i1._parameter : 0) - (i2 ? i2._parameter : 0) : curve21 && curve22 ? curve21.getIndex() - curve22.getIndex() : curve21 ? 1 : -1; diff --git a/src/path/PathItem.Boolean.js b/src/path/PathItem.Boolean.js index 35e64017..04238033 100644 --- a/src/path/PathItem.Boolean.js +++ b/src/path/PathItem.Boolean.js @@ -225,14 +225,12 @@ PathItem.inject(new function() { intersections.forEach(function(inter) { var log = ['CurveLocation', inter._id, 'p', inter.getPath()._id, 'i', inter.getIndex(), 't', inter._parameter, - 'i2', inter._curve2 ? inter._curve2.getIndex() : null, - 't2', inter._parameter2, 'o', !!inter._overlap]; + 'o', !!inter._overlap]; if (inter._other) { - inter = inter.getIntersection(); + inter = inter._intersection; log.push('Other', inter._id, 'p', inter.getPath()._id, 'i', inter.getIndex(), 't', inter._parameter, - 'i2', inter._curve2 ? inter._curve2.getIndex() : null, - 't2', inter._parameter2, 'o', !!inter._overlap); + 'o', !!inter._overlap); } console.log(log.map(function(v) { return v == null ? '-' : v @@ -275,7 +273,7 @@ PathItem.inject(new function() { clearSegments.push(segment); } // Link the new segment with the intersection on the other curve - segment._intersection = loc.getIntersection(); + segment._intersection = loc._intersection; loc._segment = segment; prev = loc; } diff --git a/src/path/PathItem.js b/src/path/PathItem.js index a4346539..c370c98a 100644 --- a/src/path/PathItem.js +++ b/src/path/PathItem.js @@ -110,11 +110,13 @@ var PathItem = Item.extend(/** @lends PathItem# */{ startConnected: length1 === 1, // After splitting, the end is always connected: endConnected: true, - adjust: function(loc) { + reparametrize: function(t1, t2) { // Since the curve was split above, we need to // adjust the parameters for both locations. - loc._parameter /= 2; - loc._parameter2 = 0.5 + loc._parameter2 / 2; + return { + t1: t1 / 2, + t2: (1 + t2) / 2 + }; } } );