Simplify CurveLocation data structures.

Directly creating and linking intersections simplifies things a lot.
This commit is contained in:
Jürg Lehni 2015-09-09 17:17:49 +02:00
parent 78e0bae6aa
commit 04452730dd
5 changed files with 74 additions and 90 deletions

View file

@ -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);

View file

@ -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;

View file

@ -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;

View file

@ -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;
}

View file

@ -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
};
}
}
);