mirror of
https://github.com/scratchfoundation/paper.js.git
synced 2025-01-03 19:45:44 -05:00
Imrpove bi-directional curve-time rescaling in divideLocations()
Closes #1191
This commit is contained in:
parent
e24402542a
commit
468bb04919
1 changed files with 77 additions and 32 deletions
|
@ -243,6 +243,12 @@ PathItem.inject(new function() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function clearCurveHandles(curves) {
|
||||||
|
// Clear segment handles if they were part of a curve with no handles.
|
||||||
|
for (var i = curves.length - 1; i >= 0; i--)
|
||||||
|
curves[i].clearHandles();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Divides the path-items at the given locations.
|
* Divides the path-items at the given locations.
|
||||||
*
|
*
|
||||||
|
@ -254,39 +260,71 @@ PathItem.inject(new function() {
|
||||||
* were divided
|
* were divided
|
||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
function divideLocations(locations, include) {
|
function divideLocations(locations, include, clearLater) {
|
||||||
var results = include && [],
|
var results = include && [],
|
||||||
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
tMin = /*#=*/Numerical.CURVETIME_EPSILON,
|
||||||
tMax = 1 - tMin,
|
tMax = 1 - tMin,
|
||||||
noHandles = false,
|
clearHandles = false,
|
||||||
clearCurves = [],
|
clearCurves = clearLater || [],
|
||||||
|
clearLookup = clearLater && {},
|
||||||
|
rescaleLocs,
|
||||||
prevCurve,
|
prevCurve,
|
||||||
prevTime;
|
prevTime;
|
||||||
|
|
||||||
|
// When dealing with overlaps and crossings, divideLocations() is called
|
||||||
|
// twice. If curve handles of curves that originally didn't have handles
|
||||||
|
// are cleared after the first call , we loose curve-time consistency
|
||||||
|
// and CurveLocation#_time values become invalid.
|
||||||
|
// In those situations, clearLater is passed as a container for all
|
||||||
|
// curves of which the handles need to be cleared in the end.
|
||||||
|
// Create a lookup table that allows us to quickly determine if a given
|
||||||
|
// curve was resulting from an original curve without handles.
|
||||||
|
function getId(curve) {
|
||||||
|
return curve._path._id + '.' + curve._segment1._index;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (var i = (clearLater && clearLater.length) - 1; i >= 0; i--) {
|
||||||
|
var curve = clearLater[i];
|
||||||
|
if (curve._path)
|
||||||
|
clearLookup[getId(curve)] = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Loop backwards through all sorted locations, from right to left, so
|
||||||
|
// we can assume a predefined sequence for curve-time rescaling.
|
||||||
for (var i = locations.length - 1; i >= 0; i--) {
|
for (var i = locations.length - 1; i >= 0; i--) {
|
||||||
var loc = locations[i],
|
var loc = locations[i],
|
||||||
// Retrieve curve-time before calling include(), because it may
|
// Retrieve curve-time before calling include(), because it may
|
||||||
// be changed to the scaled value after splitting previously.
|
// be changed to the scaled value after splitting previously.
|
||||||
// See CurveLocation#getCurve(), #resolveCrossings()
|
// See CurveLocation#getCurve(), #resolveCrossings()
|
||||||
time = loc._time;
|
time = loc._time,
|
||||||
if (include) {
|
exclude = include && !include(loc),
|
||||||
if (!include(loc))
|
// Retrieve curve after calling include(), because it may cause
|
||||||
continue;
|
// a change in the cached location values, see above.
|
||||||
|
curve = loc._curve,
|
||||||
|
segment;
|
||||||
|
if (curve && curve !== prevCurve) {
|
||||||
|
// This is a new curve, update clearHandles setting.
|
||||||
|
clearHandles = !curve.hasHandles()
|
||||||
|
|| clearLookup && clearLookup[getId(curve)];
|
||||||
|
// Only keep track of information for rescaling within the curve
|
||||||
|
rescaleLocs = [];
|
||||||
|
prevTime = null;
|
||||||
|
} else if (prevTime > tMin) {
|
||||||
|
// Rescale curve-time when we are splitting the same curve
|
||||||
|
// multiple times, if splitting was done previously.
|
||||||
|
loc._time /= prevTime;
|
||||||
|
}
|
||||||
|
prevCurve = curve;
|
||||||
|
if (exclude) {
|
||||||
|
// Store this excluded location for later rescaling, in case we
|
||||||
|
// divide the same curve further to the left of this location.
|
||||||
|
rescaleLocs.push(loc);
|
||||||
|
continue;
|
||||||
|
} else if (include) {
|
||||||
results.unshift(loc);
|
results.unshift(loc);
|
||||||
}
|
}
|
||||||
// Retrieve curve after calling include(), because it may cause a
|
prevTime = time;
|
||||||
// change in the cached location values, see above.
|
time = loc._time;
|
||||||
var curve = loc._curve,
|
|
||||||
origTime = time,
|
|
||||||
segment;
|
|
||||||
if (curve !== prevCurve) {
|
|
||||||
// This is a new curve, update noHandles setting.
|
|
||||||
noHandles = !curve.hasHandles();
|
|
||||||
} else if (prevTime > tMin) {
|
|
||||||
// Scale parameter when we are splitting same curve multiple
|
|
||||||
// times, but only if splitting was done previously.
|
|
||||||
time /= prevTime;
|
|
||||||
}
|
|
||||||
if (time < tMin) {
|
if (time < tMin) {
|
||||||
segment = curve._segment1;
|
segment = curve._segment1;
|
||||||
} else if (time > tMax) {
|
} else if (time > tMax) {
|
||||||
|
@ -298,9 +336,15 @@ PathItem.inject(new function() {
|
||||||
var newCurve = curve.divideAtTime(time, true);
|
var newCurve = curve.divideAtTime(time, true);
|
||||||
// Keep track of curves without handles, so they can be cleared
|
// Keep track of curves without handles, so they can be cleared
|
||||||
// again at the end.
|
// again at the end.
|
||||||
if (noHandles)
|
if (clearHandles)
|
||||||
clearCurves.push(curve, newCurve);
|
clearCurves.push(curve, newCurve);
|
||||||
segment = newCurve._segment1;
|
segment = newCurve._segment1;
|
||||||
|
// If there are locations to be rescaled within the same curve
|
||||||
|
// after this location, we need to rescale their curve-time now.
|
||||||
|
for (var j = rescaleLocs.length - 1; j >= 0; j--) {
|
||||||
|
var l = rescaleLocs[j];
|
||||||
|
l._time = (l._time - time) / (1 - time);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
loc._setSegment(segment);
|
loc._setSegment(segment);
|
||||||
// Create links from the new segment to the intersection on the
|
// Create links from the new segment to the intersection on the
|
||||||
|
@ -321,14 +365,10 @@ PathItem.inject(new function() {
|
||||||
} else {
|
} else {
|
||||||
segment._intersection = dest;
|
segment._intersection = dest;
|
||||||
}
|
}
|
||||||
prevCurve = curve;
|
|
||||||
prevTime = origTime;
|
|
||||||
}
|
|
||||||
// Clear segment handles if they were part of a curve with no handles,
|
|
||||||
// once we are done with the entire curve.
|
|
||||||
for (var i = 0, l = clearCurves.length; i < l; i++) {
|
|
||||||
clearCurves[i].clearHandles();
|
|
||||||
}
|
}
|
||||||
|
// Clear curve handles right away if we're not storing them for later.
|
||||||
|
if (!clearLater)
|
||||||
|
clearCurveHandles(clearCurves);
|
||||||
return results || locations;
|
return results || locations;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -615,7 +655,7 @@ PathItem.inject(new function() {
|
||||||
// from the point (horizontal or vertical), based on the
|
// from the point (horizontal or vertical), based on the
|
||||||
// curve's direction at that point. If the tangent is less
|
// curve's direction at that point. If the tangent is less
|
||||||
// than 45°, cast the ray vertically, else horizontally.
|
// than 45°, cast the ray vertically, else horizontally.
|
||||||
dir = abs(curve.getTangentAtTime(t).normalize().y)
|
dir = abs(curve.getTangentAtTime(t).normalize().y)
|
||||||
< Math.SQRT1_2 ? 1 : 0;
|
< Math.SQRT1_2 ? 1 : 0;
|
||||||
if (parent instanceof CompoundPath)
|
if (parent instanceof CompoundPath)
|
||||||
path = parent;
|
path = parent;
|
||||||
|
@ -1011,14 +1051,17 @@ PathItem.inject(new function() {
|
||||||
intersections = this.getIntersections(null, function(inter) {
|
intersections = this.getIntersections(null, function(inter) {
|
||||||
return inter._overlap && (hasOverlaps = true) ||
|
return inter._overlap && (hasOverlaps = true) ||
|
||||||
inter.isCrossing() && (hasCrossings = true);
|
inter.isCrossing() && (hasCrossings = true);
|
||||||
});
|
}),
|
||||||
|
// We only need to keep track of curves that need clearing
|
||||||
|
// outside of divideLocations() if two calls are necessary.
|
||||||
|
clearCurves = hasOverlaps && hasCrossings && [];
|
||||||
intersections = CurveLocation.expand(intersections);
|
intersections = CurveLocation.expand(intersections);
|
||||||
if (hasOverlaps) {
|
if (hasOverlaps) {
|
||||||
// First divide in all overlaps, and then remove the inside of
|
// First divide in all overlaps, and then remove the inside of
|
||||||
// the resulting overlap ranges.
|
// the resulting overlap ranges.
|
||||||
var overlaps = divideLocations(intersections, function(inter) {
|
var overlaps = divideLocations(intersections, function(inter) {
|
||||||
return inter._overlap;
|
return inter._overlap;
|
||||||
});
|
}, clearCurves);
|
||||||
for (var i = overlaps.length - 1; i >= 0; i--) {
|
for (var i = overlaps.length - 1; i >= 0; i--) {
|
||||||
var seg = overlaps[i]._segment,
|
var seg = overlaps[i]._segment,
|
||||||
prev = seg.getPrevious(),
|
prev = seg.getPrevious(),
|
||||||
|
@ -1059,7 +1102,9 @@ PathItem.inject(new function() {
|
||||||
// handling of overlaps, to not confuse tracePaths().
|
// handling of overlaps, to not confuse tracePaths().
|
||||||
seg._intersection = null;
|
seg._intersection = null;
|
||||||
}
|
}
|
||||||
});
|
}, clearCurves);
|
||||||
|
if (clearCurves)
|
||||||
|
clearCurveHandles(clearCurves);
|
||||||
// Finally resolve self-intersections through tracePaths()
|
// Finally resolve self-intersections through tracePaths()
|
||||||
paths = tracePaths(Base.each(paths, function(path) {
|
paths = tracePaths(Base.each(paths, function(path) {
|
||||||
this.push.apply(this, path._segments);
|
this.push.apply(this, path._segments);
|
||||||
|
|
Loading…
Reference in a new issue